view th_printf.c @ 704:b6b8e7249666

Check that the buffer length is >= 1 before trying to access buf[len - 1]
author Matti Hamalainen <ccr@tnsp.org>
date Sun, 26 Apr 2020 23:34:21 +0300
parents 284d5b789b7c
children 4ca6a3b30fe8
line wrap: on
line source

/*
 * A simple and incomplete printf() implementation
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2016-2020 Tecnic Software productions (TNSP)
 *
 * Please read file 'COPYING' for information on license and distribution.
 */


#ifdef TH_PRINTF_DEBUG
BOOL th_printf_debug = FALSE;
char *th_printf_debug_prefix = NULL;


static void pflag(char *buf, const char *str, const int sep, const int flags, const int flg)
{
    strcat(buf, (flags & flg) ? str : "   ");
    if (sep)
        strcat(buf, "|");
}


static const char *get_flags(const int flags)
{
    static char buf[256];

    buf[0] = 0;

    pflag(buf, "ALT", 1, flags, TH_PF_ALT);
    pflag(buf, "SGN", 1, flags, TH_PF_SIGN);
    pflag(buf, "SPC", 1, flags, TH_PF_SPACE);
    pflag(buf, "GRP", 1, flags, TH_PF_GROUP);
    pflag(buf, "ZER", 1, flags, TH_PF_ZERO);
    pflag(buf, "LFT", 0, flags, TH_PF_LEFT);

    return buf;
}

#define PP_LINE(...) \
    do { \
        if (th_printf_debug) { \
            if (th_printf_debug_prefix != NULL) \
                fputs(th_printf_debug_prefix, stdout); \
            fprintf(stdout, __VA_ARGS__); \
        } \
    } while (0)

#define PP_PRINTF(...) \
    do { \
        if (th_printf_debug) { \
            fprintf(stdout, __VA_ARGS__); \
        } \
    } while (0)

#else
#define PP_LINE(...) /* stub */
#define PP_PRINTF(...) /* stub */
#endif


static int th_vprintf_put_pstr(th_vprintf_ctx *ctx, th_vprintf_putch vputch, const char *str)
{
    while (*str)
    {
        int ret;
        if ((ret = vputch(ctx, *str++)) == EOF)
            return ret;
    }
    return 0;
}


static int th_vprintf_put_repch(th_vprintf_ctx *ctx, th_vprintf_putch vputch, int count, const char ch)
{
    while (count-- > 0)
    {
        int ret;
        if ((ret = vputch(ctx, ch)) == EOF)
            return ret;
    }
    return 0;
}


static int th_printf_pad_pre(th_vprintf_ctx *ctx, th_vprintf_putch vputch,
    int f_width, const int f_flags)
{
    if (f_width > 0 && (f_flags & TH_PF_LEFT) == 0)
        return th_vprintf_put_repch(ctx, vputch, f_width, ' ');
    else
        return 0;
}


static int th_printf_pad_post(th_vprintf_ctx *ctx, th_vprintf_putch vputch,
    int f_width, const int f_flags)
{
    if (f_width > 0 && (f_flags & TH_PF_LEFT))
        return th_vprintf_put_repch(ctx, vputch, f_width, ' ');
    else
        return 0;
}


#define TH_VPRINTF_INTFMT_NAME th_vprintf_buf_int
#define TH_VPRINTF_INTFMT_TYPE_S int
#define TH_VPRINTF_INTFMT_TYPE_U unsigned int
#include "th_printf1.c"


#define TH_VPRINTF_INTFMT_NAME th_vprintf_buf_int64
#define TH_VPRINTF_INTFMT_TYPE_S int64_t
#define TH_VPRINTF_INTFMT_TYPE_U uint64_t
#include "th_printf1.c"


TH_VPRINTF_ALTFMT_FUNC(th_vprintf_altfmt_oct)
{
    (void) vret;
    (void) flags;
    (void) prec;

    // This is not very nice
    if (len < 1 || buf[len - 1] != '0')
    {
        if (*prec > 0)
            (*prec)--;

        *outlen = 1;
        return "0";
    }
    else
        return NULL;
}


TH_VPRINTF_ALTFMT_FUNC(th_vprintf_altfmt_hex)
{
    (void) buf;
    (void) len;
    (void) prec;

    if (vret != 0)
    {
        *outlen = 2;
        return (*flags & TH_PF_UPCASE) ? "0X" : "0x";
    }
    else
        return NULL;
}


int th_vprintf_put_int_format(th_vprintf_ctx *ctx, th_vprintf_putch vputch,
    th_char_t *buf, int f_flags, int f_width, int f_prec, int f_len, int vret,
    BOOL f_neg, BOOL f_unsig, th_vprintf_altfmt_func f_alt)
{
    int ret = 0, nspacepad, nzeropad, f_altstr_len = 0;
    th_char_t f_sign, *f_altstr;

    // Special case for value of 0
    if (vret == 0)
    {
        // For NULL pointers, use "(nil)"
        if (f_flags & TH_PF_POINTER)
        {
            strcpy(buf, ")lin(");
            f_len = 5;
        }
        else
        if (f_prec != 0)
        {
            buf[f_len++] = '0';
            buf[f_len] = 0;
        }
    }

    // Get alternative format string, if needed and available
    f_altstr = (f_flags & TH_PF_ALT) && f_alt != NULL ?
        f_alt(buf, f_len, vret, &f_prec, &f_flags, &f_altstr_len) : NULL;

    // Are we using a sign prefix?
    f_sign = f_unsig ? 0 : ((f_flags & TH_PF_SIGN) ?
        (f_neg ? '-' : '+') :
        (f_neg ? '-' : ((f_flags & TH_PF_SPACE) ? ' ' : 0)));

    // Check for left padding, negates zero prefix padding
    if (f_flags & TH_PF_LEFT)
        f_flags &= ~TH_PF_ZERO;


    // Calculate necessary padding, etc
    //
    // XXX TODO FIXME: The logic here is not very elegant
    // and would benefit from commenting on how it works.
    // Also same goes for the th_vprintf_altfmt_* logic.
    //
    int nlen = (f_sign ? 1 : 0) + f_altstr_len;
    int qlen = (f_prec > f_len ? f_prec : f_len) + nlen;

    if (f_flags & TH_PF_POINTER && vret == 0)
    {
        PP_LINE("^");
        qlen = f_len + nlen;
        nspacepad = f_width > qlen ? f_width - qlen : 0;
        nzeropad = 0;
    }
    else
    if ((f_flags & TH_PF_ZERO) && f_prec < 0 && f_width > 0)
    {
        PP_LINE("#");
        nzeropad = f_width - qlen;
        nspacepad = 0;
    }
    else
    {
        PP_LINE("$");
        nzeropad = (f_prec >= 0) ? f_prec - f_len : 0;
        nspacepad = (f_width >= 0) ? f_width - qlen : 0;
    }

    PP_PRINTF(": vret=%3d, f_flags=[%s], f_unsig=%d, f_sign='%c', f_len=%3d, f_width=%3d, f_prec=%3d, nspacepad=%3d, nzeropad=%3d, qlen=%3d\n",
        vret, get_flags(f_flags), f_unsig, f_sign ? f_sign : '?', f_len, f_width, f_prec, nspacepad, nzeropad, qlen);


    // Do prefix padding, if any
    if ((ret = th_printf_pad_pre(ctx, vputch, nspacepad, f_flags)) == EOF)
        return ret;

    // Sign prefix
    if (f_sign && (ret = vputch(ctx, f_sign)) == EOF)
        return ret;

    // Alternative format string
    if (f_altstr && (ret = th_vprintf_put_pstr(ctx, vputch, f_altstr)) == EOF)
        return ret;

    // Zero padding
    if (nzeropad > 0 && (ret = th_vprintf_put_repch(ctx, vputch, nzeropad, '0')) == EOF)
        return ret;

    // Output the value
    while (f_len-- > 0)
    {
        if ((ret = vputch(ctx, buf[f_len])) == EOF)
            return ret;
    }

    // Do postfix padding, if any
    return th_printf_pad_post(ctx, vputch, nspacepad, f_flags);
}


int th_vprintf_put_int(th_vprintf_ctx *ctx, th_vprintf_putch vputch,
    va_list ap, const int f_radix, int f_flags, int f_width, int f_prec,
    const BOOL f_unsig, th_vprintf_altfmt_func f_alt)
{
    th_char_t buf[32];
    int f_len = 0, vret;
    BOOL f_neg = FALSE;

    if (f_flags & TH_PF_LONGLONG)
    {
        vret = th_vprintf_buf_int64(buf, sizeof(buf), &f_len, va_arg(ap, int64_t),
            f_radix, f_flags & TH_PF_UPCASE, f_unsig, &f_neg);
    }
    else
    {
       vret = th_vprintf_buf_int(buf, sizeof(buf), &f_len, va_arg(ap, unsigned int),
            f_radix, f_flags & TH_PF_UPCASE, f_unsig, &f_neg);
    }

    if (vret == EOF)
        return 0;

    return th_vprintf_put_int_format(ctx, vputch, buf, f_flags, f_width, f_prec, f_len, vret, f_neg, f_unsig, f_alt);
}


int th_vprintf_put_str(th_vprintf_ctx *ctx, th_vprintf_putch vputch,
    const th_char_t *str, int f_flags, const int f_width, const int f_prec)
{
    int nspacepad, f_len, ret;

    // Check for null strings
    if (str == NULL)
    {
        // If the "(null)" string does not fit precision, return empty
        if (f_prec >= 0 && f_prec < 6)
            return 0;

        str = "(null)";
    }

    // Compute padding etc
    f_len = strlen(str);
    if (f_prec >= 0 && f_len > f_prec)
        f_len = f_prec;

    nspacepad = f_width - f_len;

    // Do prefix padding, if any
    if ((ret = th_printf_pad_pre(ctx, vputch, nspacepad, f_flags)) == EOF)
        return ret;

    while (*str && f_len--)
    {
        if ((ret = vputch(ctx, *str++)) == EOF)
            return ret;
    }

    // Do postfix padding, if any
    return th_printf_pad_post(ctx, vputch, nspacepad, f_flags);
}


#ifdef TH_WIP_FLOAT_SUPPORT
int th_vprintf_put_float(th_vprintf_ctx *ctx, th_vprintf_putch vputch,
    va_list ap, int f_flags, int f_width, int f_prec)
{
    double pval = va_arg(ap, double);   // This needs to be double for type promotion to occur
    int64_t val;

    // This is a hack, trying to avoid dereferencing a type-punned
    // pointer and all that stuff related to strict aliasing (although
    // double and int64_t should be same size and have same aliasing rules.)
    memcpy(&val, &pval, sizeof(int64_t));

    // We have sign, exponent and mantissa
    BOOL f_sign    = (val >> 63) & 0x01;
    int64_t d_exp  = (val >> 52) & 0x7ff;
    uint64_t d_man = val & 0x0fffffffffffff;

    return 0;
}
#endif


int th_vprintf_do_format(
    th_vprintf_ctx *ctx, th_vprintf_putch vputch,
    int f_width, int f_prec, int f_flags,
    const th_char_t fmt, va_list ap)
{
    int ret;

    switch (fmt)
    {
        case 0:
            // Unexpected end of format string
            return -104;

        case 'c':
            if ((ret = th_printf_pad_pre(ctx, vputch, f_width - 1, f_flags)) == EOF)
                return ret;

            if ((ret = vputch(ctx, va_arg(ap, int))) == EOF)
                return ret;

            return th_printf_pad_post(ctx, vputch, f_width - 1, f_flags);

        case 'o':
            return th_vprintf_put_int(ctx, vputch, ap, 8, f_flags, f_width, f_prec, TRUE, th_vprintf_altfmt_oct);

        case 'u':
        case 'i':
        case 'd':
            return th_vprintf_put_int(ctx, vputch, ap, 10, f_flags, f_width, f_prec, fmt == 'u', NULL);

        case 'x':
        case 'X':
            if (fmt == 'X')
                f_flags |= TH_PF_UPCASE;

            return th_vprintf_put_int(ctx, vputch, ap, 16, f_flags, f_width, f_prec, TRUE, th_vprintf_altfmt_hex);

        case 'p':
            // Check for invalid flags
            if (f_flags & (TH_PF_LONG | TH_PF_LONGLONG))
                return -120;

#if (TH_PTRSIZE == 32)
            f_flags |= TH_PF_LONG;
#elif (TH_PTRSIZE == 64)
            f_flags |= TH_PF_LONGLONG;
#endif
            f_flags |= TH_PF_ALT | TH_PF_POINTER;

            return th_vprintf_put_int(ctx, vputch, ap, 16, f_flags, f_width, f_prec, TRUE, th_vprintf_altfmt_hex);

#ifdef TH_WIP_FLOAT_SUPPORT
        case 'f':
        case 'F':
            return th_vprintf_put_float(ctx, vputch, ap,
                f_flags, f_width, f_prec);
#endif

        case 's':
            return th_vprintf_put_str(ctx, vputch, va_arg(ap, th_char_t *),
                f_flags, f_width, f_prec);

        //case '%':
        default:
            return vputch(ctx, fmt);
    }
}


int th_vprintf_do(th_vprintf_ctx *ctx, th_vprintf_putch vputch, const th_char_t *fmt, va_list ap)
{
    int ret = 0;

    while (*fmt)
    {
        if (*fmt != '%')
        {
            if ((ret = vputch(ctx, *fmt)) == EOF)
                goto out;
        }
        else
        {
            int f_width = -1, f_prec = -1, f_flags = 0;
            BOOL end = FALSE;

            fmt++;

            // Check for flags
            while (!end)
            {
                switch (*fmt)
                {
                    case '#':
                        f_flags |= TH_PF_ALT;
                        break;

                    case '+':
                        f_flags |= TH_PF_SIGN;
                        break;

                    case '0':
                        f_flags |= TH_PF_ZERO;
                        break;

                    case '-':
                        f_flags |= TH_PF_LEFT;
                        break;

                    case ' ':
                        f_flags |= TH_PF_SPACE;
                        break;

                    case '\'':
                        f_flags |= TH_PF_GROUP;
                        break;

                    default:
                        end = TRUE;
                        break;
                }
                if (!end) fmt++;
            }

            // Get field width
            if (*fmt == '*')
            {
                fmt++;
                f_width = va_arg(ap, int);
                if (f_width < 0)
                {
                    f_flags |= TH_PF_LEFT;
                    f_width = -f_width;
                }
            }
            else
            {
                f_width = 0;
                while (th_isdigit(*fmt))
                    f_width = f_width * 10 + (*fmt++ - '0');
            }

            // Check for field precision
            if (*fmt == '.')
            {
                fmt++;
                if (*fmt == '*')
                {
                    fmt++;
                    f_prec = va_arg(ap, int);
                }
                else
                {
                    // If no digit after '.', precision is to be 0
                    f_prec = 0;
                    while (th_isdigit(*fmt))
                        f_prec = f_prec * 10 + (*fmt++ - '0');
                }
            }


            // Check for length modifiers (only some are supported currently)
            switch (*fmt)
            {
                case 'l':
                    if (*++fmt == 'l')
                    {
                        f_flags |= TH_PF_LONGLONG;
                        fmt++;
                    }
                    else
                        f_flags |= TH_PF_LONG;
                    break;

                case 'L':
                    fmt++;
                    f_flags |= TH_PF_LONGLONG;
                    break;

                case 'h':
                case 'j':
                case 'z':
                case 't':
                    // Unsupported for now
                    return -202;
            }

            ret = th_vprintf_do_format(ctx, vputch, f_width, f_prec, f_flags, *fmt, ap);
            if (ret == EOF)
                goto out;
            if (ret < 0)
                return ret;

        }
        fmt++;
    }

out:
    return ret == EOF ? ret : ctx->ipos;
}