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 2019 Joyent, Inc.
  14  */
  15 
  16 #include <errno.h>
  17 #include <limits.h>
  18 #include <stdio.h>
  19 #include <stdlib.h>
  20 #include <string.h>
  21 #include <sys/sysmacros.h>
  22 #include <demangle-sys.h>
  23 #include "demangle_test.h"
  24 
  25 #define ALL_TESTS ULONG_MAX
  26 
  27 SET_DECLARE(demangle_tests, demtest_group_t);
  28 SET_DECLARE(demangle_mtests, demtest_mgroup_t);
  29 
  30 static boolean_t
  31 check_failure(size_t i, demtest_case_t *tc, const char *dem, boolean_t res)
  32 {
  33         int savederr = errno;
  34 
  35         if (dem == NULL && (savederr == EINVAL || savederr == ENAMETOOLONG))
  36                 return (B_TRUE);
  37 
  38         if (res)
  39                 (void) printf("FAILURE\n");
  40 
  41         if (dem != NULL) {
  42                 (void) printf("  [%zu] Successfully demanged an invalid "
  43                     "name\n", i);
  44                 (void) printf("         Name: '%s'\n", tc->dtc_mangled);
  45                 (void) printf("    Demangled: '%s'\n", dem);
  46                 return (B_FALSE);
  47         }
  48 
  49         (void) printf("  [%zu] demangle() returned an unexpected error\n", i);
  50         (void) printf("     Name: '%s'\n", tc->dtc_mangled);
  51         (void) printf("    Errno: %d %s\n", savederr, strerror(savederr));
  52         return (B_FALSE);
  53 }
  54 
  55 static boolean_t
  56 check_success(size_t i, demtest_case_t *tc, const char *dem, boolean_t res)
  57 {
  58         if (dem != NULL && strcmp(tc->dtc_demangled, dem) == 0)
  59                 return (B_TRUE);
  60 
  61         if (res)
  62                 (void) printf("FAILURE\n");
  63 
  64         if (dem == NULL) {
  65                 (void) printf("  [%zu] Failed to demangle '%s'\n", i,
  66                     tc->dtc_mangled);
  67                 return (B_FALSE);
  68         }
  69 
  70         (void) printf("  [%zu] Demangled results do not match.\n", i);
  71         (void) printf("       Mangled: %s\n", tc->dtc_mangled);
  72         (void) printf("      Expected: %s\n", tc->dtc_demangled);
  73         (void) printf("        Actual: %s\n", dem);
  74         return (B_FALSE);
  75 }
  76 
  77 static boolean_t
  78 check_msuccess(size_t tcnum, demtest_mcase_t *tm, const char *dem,
  79     boolean_t res)
  80 {
  81         if (dem != NULL) {
  82                 for (size_t i = 0; tm->dtmc_demangled[i] != NULL; i++) {
  83                         if (strcmp(tm->dtmc_demangled[i], dem) == 0)
  84                                 return (B_TRUE);
  85                 }
  86         }
  87 
  88         if (res)
  89                 (void) printf("FAILURE\n");
  90 
  91         if (dem == NULL) {
  92                 (void) printf("  [%zu] Failed to demangle '%s'\n", tcnum,
  93                     tm->dtmc_mangled);
  94                 return (B_FALSE);
  95         }
  96 
  97         (void) printf("  [%zu] Demangled results do not match.\n", tcnum);
  98         (void) printf("       Mangled: %s\n", tm->dtmc_mangled);
  99         (void) printf("      Expected: ");
 100 
 101         for (size_t i = 0; tm->dtmc_demangled[i] != NULL; i++) {
 102                 if (i > 0) {
 103                         int indent = sizeof ("      Expected: ") - 1;
 104                         (void) printf("%*s", indent, "");
 105                 }
 106 
 107                 (void) printf("%s%s", tm->dtmc_demangled[i]);
 108 
 109                 if (tm->dtmc_demangled[i + 1] != NULL)
 110                         (void) printf(" or");
 111 
 112                 (void) fputc('\n', stdout);
 113         }
 114 
 115         (void) printf("        Actual: %s\n", dem);
 116         return (B_FALSE);
 117 }
 118 
 119 static boolean_t
 120 run_test(demtest_group_t *test, unsigned long testnum)
 121 {
 122         boolean_t res = B_TRUE;
 123 
 124         (void) printf("Test %s: ", test->dtg_name);
 125 
 126         for (size_t i = 0; test->dtg_tests[i].dtc_mangled != NULL; i++) {
 127                 char *dem;
 128 
 129                 if (testnum != ALL_TESTS && i != testnum)
 130                         continue;
 131 
 132                 dem = sysdemangle(test->dtg_tests[i].dtc_mangled,
 133                     test->dtg_lang, NULL);
 134 
 135                 if (test->dtg_tests[i].dtc_demangled == NULL)
 136                         res &= check_failure(i, &test->dtg_tests[i], dem, res);
 137                 else
 138                         res &= check_success(i, &test->dtg_tests[i], dem, res);
 139 
 140                 free(dem);
 141         }
 142 
 143         if (res)
 144                 (void) printf("SUCCESS\n");
 145 
 146         return (res);
 147 }
 148 
 149 static boolean_t
 150 run_mtest(demtest_mgroup_t *test, unsigned long testnum)
 151 {
 152         boolean_t res = B_TRUE;
 153 
 154         (void) printf("Test %s: ", test->dtmg_name);
 155 
 156         for (size_t i = 0; test->dtmg_tests[i] != NULL; i++) {
 157                 char *dem;
 158 
 159                 if (testnum != ALL_TESTS && i != testnum)
 160                         continue;
 161 
 162                 dem = sysdemangle(test->dtmg_tests[i]->dtmc_mangled,
 163                     test->dtmg_lang, NULL);
 164                 res &= check_msuccess(i, test->dtmg_tests[i], dem, res);
 165                 free(dem);
 166         }
 167 
 168         if (res)
 169                 (void) printf("SUCCESS\n");
 170 
 171         return (res);
 172 }
 173 
 174 static void
 175 list_groups(void)
 176 {
 177         size_t n;
 178 
 179         n = SET_COUNT(demangle_tests);
 180 
 181         if (n > 0)
 182                 (void) printf("Demangle tests:\n");
 183 
 184         for (size_t i = 0; i < n; i++) {
 185                 demtest_group_t *grp = SET_ITEM(demangle_tests, n - i - 1);
 186                 size_t ntest = 0;
 187 
 188                 for (size_t j = 0; grp->dtg_tests[j].dtc_mangled != NULL; j++)
 189                         ntest++;
 190 
 191                 (void) printf("\t%s (%zu tests)\n", grp->dtg_name, ntest);
 192         }
 193 
 194 
 195         if (n > 0)
 196                 (void) fputc('\n', stdout);
 197 
 198         n = SET_COUNT(demangle_mtests);
 199 
 200         if (n > 0)
 201                 (void) printf("Demangle mtests:\n");
 202 
 203         for (size_t i = 0; i < n; i++) {
 204                 demtest_mgroup_t *mgrp = SET_ITEM(demangle_mtests, n - i - 1);
 205                 size_t ntest = 0;
 206 
 207                 for (size_t j = 0; mgrp->dtmg_tests[j] != NULL; j++)
 208                         ntest++;
 209 
 210                 (void) printf("\t%s (%zu tests)\n", mgrp->dtmg_name, ntest);
 211         }
 212 
 213 }
 214 
 215 __NORETURN static void
 216 usage(const char *name)
 217 {
 218         (void) fprintf(stderr,
 219             "%s: [-hl] { [-g group] | [-m mgroup] } [-t test]\n", name);
 220         (void) fprintf(stderr, "    -h        Display this help\n");
 221         (void) fprintf(stderr, "    -l        List test groups\n");
 222         (void) fprintf(stderr, "    -g group  Only run the specified group\n");
 223         (void) fprintf(stderr, "    -m mgroup Only run the specified mgroup\n");
 224         (void) fprintf(stderr, "    -t test  Only run the specified test "
 225             "(requires -g or -m)\n");
 226         exit(EXIT_FAILURE);
 227 }
 228 
 229 int
 230 main(int argc, char **argv)
 231 {
 232         const char *group = NULL;
 233         const char *mgroup = NULL;
 234         unsigned long testnum = ALL_TESTS;
 235         size_t i, n;
 236         int c;
 237         boolean_t ok = B_TRUE;
 238         boolean_t found;
 239 
 240         while ((c = getopt(argc, argv, "hlg:m:t:")) > 0) {
 241                 switch (c) {
 242                 case 'h':
 243                         usage(argv[0]);
 244                 case 'l':
 245                         list_groups();
 246                         return (EXIT_SUCCESS);
 247                 case 'g':
 248                         if (mgroup != NULL) {
 249                                 (void) fprintf(stderr, "-g and -m options are "
 250                                     "mutually exclusive\n");
 251                                 return (EXIT_FAILURE);
 252                         }
 253 
 254                         group = optarg;
 255                         break;
 256                 case 'm':
 257                         if (group != NULL) {
 258                                 (void) fprintf(stderr, "-g and -m options are "
 259                                     "mutually exclusive\n");
 260                                 return (EXIT_FAILURE);
 261                         }
 262 
 263                         mgroup = optarg;
 264                         break;
 265                 case 't':
 266                         errno = 0;
 267                         testnum = strtoul(optarg, NULL, 10);
 268                         if (errno != 0) {
 269                                 (void) fprintf(stderr,
 270                                     "Unable to parse '%s' as a number: %s\n",
 271                                     optarg, strerror(errno));
 272                                 return (EXIT_FAILURE);
 273                         }
 274                         break;
 275                 case '?':
 276                         (void) fprintf(stderr, "Unknown option -%c\n", optopt);
 277                         usage(argv[0]);
 278                 }
 279         }
 280 
 281         if (testnum != ALL_TESTS && group == NULL && mgroup == NULL) {
 282                 (void) fprintf(stderr, "Error: -t requires -g or -m option\n");
 283                 return (EXIT_FAILURE);
 284         }
 285 
 286         /*
 287          * A downside to using linker sets is that the items get added in
 288          * the reverse order in which they're declared. To match the
 289          * declaration order, we iterate in reverse.
 290          */
 291         n = SET_COUNT(demangle_tests);
 292         found = (group == NULL) ? B_TRUE : B_FALSE;
 293         for (i = 0; i < n; i++) {
 294                 demtest_group_t *grp = SET_ITEM(demangle_tests, n - i - 1);
 295 
 296                 if (group == NULL) {
 297                         ok &= run_test(grp, testnum);
 298                 } else if (strcmp(grp->dtg_name, group) == 0) {
 299                         found = B_TRUE;
 300                         ok &= run_test(grp, testnum);
 301                         break;
 302                 }
 303         }
 304         if (!found) {
 305                 (void) fprintf(stderr, "Test group %s does not exist\n", group);
 306                 return (EXIT_FAILURE);
 307         }
 308 
 309         n = SET_COUNT(demangle_mtests);
 310         found = (mgroup == NULL) ? B_TRUE : B_FALSE;
 311         for (i = 0; i < n; i++) {
 312                 demtest_mgroup_t *mgrp = SET_ITEM(demangle_mtests, n - i - 1);
 313 
 314                 if (mgroup == NULL) {
 315                         ok &= run_mtest(mgrp, testnum);
 316                 } else if (strcmp(mgrp->dtmg_name, mgroup) == 0) {
 317                         found = B_TRUE;
 318                         ok &= run_mtest(mgrp, testnum);
 319                         break;
 320                 }
 321         }
 322         if (!found) {
 323                 (void) fprintf(stderr,
 324                     "Test mgroup %s does not exist\n", group);
 325                 return (EXIT_FAILURE);
 326         }
 327 
 328         return (ok ? 0 : 1);
 329 }
 330 
 331 const char *
 332 _umem_debug_init(void)
 333 {
 334         return ("default,verbose");
 335 }
 336 
 337 const char *
 338 _umem_logging_init(void)
 339 {
 340         return ("fail,contents");
 341 }