root/usr/src/cmd/demangle/demangle.c
/*
 * 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 2020 Joyent, Inc.
 */

#include <ctype.h>
#include <demangle-sys.h>
#include <err.h>
#include <errno.h>
#include <libcustr.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>

#define _(x) gettext(x)

locale_t c_locale;

static int do_symbols(sysdem_lang_t, int, char * const *);
static int do_input(sysdem_lang_t, FILE *restrict, FILE *restrict);
static int do_demangle(const char *, sysdem_lang_t, FILE *);
static void appendc(custr_t *, char);
static void xputc(int, FILE *);

static void
usage(void)
{
        (void) fprintf(stderr, _("Usage: %s [-l lang] [sym...]\n"),
            getprogname());
        exit(2);
}

int
main(int argc, char * const *argv)
{
        sysdem_lang_t lang = SYSDEM_LANG_AUTO;
        int c;
        int ret;

        (void) setlocale(LC_ALL, "");

#if !defined(TEXT_DOMAIN)
#define TEXT_DOMAIN "SYS_TEST"
#endif
        (void) textdomain(TEXT_DOMAIN);

        /*
         * For detecting symbol boundaries, we want to use the C locale
         * definitions for use in isalnum_l().
         */
        if ((c_locale = newlocale(LC_CTYPE_MASK, "C", NULL)) == NULL)
                err(EXIT_FAILURE, _("failed to construct C locale"));

        while ((c = getopt(argc, argv, "hl:")) != -1) {
                switch (c) {
                case 'l':
                        if (sysdem_parse_lang(optarg, &lang))
                                break;

                        errx(EXIT_FAILURE, _("Unsupported language '%s'\n"),
                            optarg);
                case 'h':
                case '?':
                        usage();
                }
        }

        argc -= optind;
        argv += optind;

        if (argc > 0)
                ret = do_symbols(lang, argc, argv);
        else
                ret = do_input(lang, stdin, stdout);

        return ((ret < 0) ? EXIT_FAILURE : EXIT_SUCCESS);
}

static int
do_symbols(sysdem_lang_t lang, int argc, char * const *argv)
{
        int ret = 0;

        for (int i = 0; i < argc; i++) {
                if (do_demangle(argv[i], lang, stdout) < 0)
                        ret = -1;
                else
                        xputc('\n', stdout);
        }

        return (ret);
}

static int
do_input(sysdem_lang_t lang, FILE *restrict in, FILE *restrict out)
{
        custr_t *word = NULL;
        int c;
        int ret = 0;
        boolean_t in_symbol = B_FALSE;

        if (custr_alloc(&word) != 0)
                err(EXIT_FAILURE, _("failed to allocate memory"));

        while ((c = fgetc(in)) != EOF) {
                if (in_symbol) {
                        /*
                         * All currently supported mangling formats only use
                         * alphanumeric characters, '.', '_', or '$' in
                         * mangled names. Once we've seen the potential start
                         * of a symbol ('_'), we accumulate subsequent
                         * charaters into 'word'. If we encounter a character
                         * that is not a part of that set ([A-Za-z0-9._$]), we
                         * treat it as a delimiter, we stop accumulating
                         * characters into word, and we attempt to demangle the
                         * accumulated string in 'word' by calling
                         * demangle_custr().
                         *
                         * Similar utilities like c++filt behave in a similar
                         * fashion when reading from stdin to allow for
                         * demangling of symbols embedded in surrounding text.
                         */
                        if (isalnum_l(c, c_locale) || c == '.' || c == '_' ||
                            c == '$') {
                                appendc(word, c);
                                continue;
                        }

                        /*
                         * Hit a symbol boundary, attempt to demangle what
                         * we've accumulated in word and reset word.
                         */
                        if (do_demangle(custr_cstr(word), lang, out) < 0)
                                ret = -1;

                        custr_reset(word);
                        in_symbol = B_FALSE;
                }

                if (c != '_') {
                        xputc(c, out);
                } else {
                        in_symbol = B_TRUE;
                        appendc(word, c);
                }
        }

        if (ferror(in))
                err(EXIT_FAILURE, _("error reading input"));

        /*
         * If we were accumulating characters for a symbol and hit EOF,
         * attempt to demangle what we accumulated.
         */
        if (custr_len(word) > 0 && do_demangle(custr_cstr(word), lang, out) < 0)
                ret = -1;

        custr_free(word);
        return (ret);
}

/*
 * Attempt to demangle 'sym' as a symbol for 'lang' and write the result
 * to 'out'. If 'sym' could not be demangled as 'lang' symbol, the original
 * string is output instead.
 *
 * If an error other than 'not a mangled symbol' is encountered (e.g. ENOMEM),
 * a warning is sent to stderr and -1 is returned. Otherwise, 0 is returned
 * (including when 'sym' is merely not a mangled symbol of 'lang').
 */
static int
do_demangle(const char *sym, sysdem_lang_t lang, FILE *out)
{
        char *demangled = sysdemangle(sym, lang, NULL);

        if (demangled == NULL && errno != EINVAL && errno != ENOTSUP) {
                warn(_("error while demangling '%s'"), sym);
                return (-1);
        }

        if (fprintf(out, "%s", (demangled != NULL) ? demangled : sym) < 0)
                err(EXIT_FAILURE, _("failed to write to output"));

        free(demangled);
        return (0);
}

static void
appendc(custr_t *cus, char c)
{
        if (custr_appendc(cus, c) == 0)
                return;
        err(EXIT_FAILURE, _("failed to save character from input"));
}

static void
xputc(int c, FILE *out)
{
        if (fputc(c, out) < 0)
                err(EXIT_FAILURE, _("failed to write output"));
}