X-Git-Url: http://nsz.repo.hu/git/?a=blobdiff_plain;f=format_check.c;h=7c8304efccc9ae8462db42a8efbe567953e865d6;hb=853c4c59c67930035ac59bd2bffbfa8a8c21b767;hp=8b9bc6396ea6f73bef619eb5a4d1990730a0b3f8;hpb=1c43487a8633dca3329e08e2c829ec9ceb13c91b;p=cparser diff --git a/format_check.c b/format_check.c index 8b9bc63..7c8304e 100644 --- a/format_check.c +++ b/format_check.c @@ -1,6 +1,6 @@ /* * This file is part of cparser. - * Copyright (C) 2007-2008 Matthias Braun + * Copyright (C) 2007-2009 Matthias Braun * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,6 +20,7 @@ #include #include +#include "adt/util.h" #include "format_check.h" #include "symbol_t.h" #include "ast_t.h" @@ -86,7 +87,7 @@ static const char* get_length_modifier_name(const format_length_modifier_t mod) [FMT_MOD_I32] = "I32", [FMT_MOD_I64] = "I64" }; - assert(mod < sizeof(names) / sizeof(*names)); + assert(mod < lengthof(names)); return names[mod]; } @@ -114,76 +115,90 @@ struct vchar_t { int (*is_digit)(unsigned vchar); }; -static unsigned string_first(vchar_t *self) { +static unsigned string_first(vchar_t *self) +{ self->position = 0; const string_t *string = self->string; return string->begin[0]; } -static unsigned string_next(vchar_t *self) { +static unsigned string_next(vchar_t *self) +{ ++self->position; const string_t *string = self->string; return string->begin[self->position]; } -static int string_isdigit(unsigned vchar) { +static int string_isdigit(unsigned vchar) +{ return isdigit(vchar); } -static unsigned wstring_first(vchar_t *self) { +static unsigned wstring_first(vchar_t *self) +{ self->position = 0; const wide_string_t *wstring = self->string; return wstring->begin[0]; } -static unsigned wstring_next(vchar_t *self) { +static unsigned wstring_next(vchar_t *self) +{ ++self->position; const wide_string_t *wstring = self->string; return wstring->begin[self->position]; } -static int wstring_isdigit(unsigned vchar) { +static int wstring_isdigit(unsigned vchar) +{ return iswdigit(vchar); } -static bool atend(vchar_t *self) { +static bool atend(vchar_t *self) +{ return self->position + 1 == self->size; } /** * Check printf-style format. */ -static void check_printf_format(const call_argument_t *arg, const format_spec_t *spec) +static int internal_check_printf_format(const expression_t *fmt_expr, + const call_argument_t *arg, const format_spec_t *spec) { - /* find format arg */ - unsigned idx = 0; - for (; idx < spec->fmt_idx; ++idx) - arg = arg->next; - - const expression_t *fmt_expr = arg->expression; if (fmt_expr->kind == EXPR_UNARY_CAST_IMPLICIT) { fmt_expr = fmt_expr->unary.value; } vchar_t vchar; - if (fmt_expr->kind == EXPR_WIDE_STRING_LITERAL) { - vchar.string = &fmt_expr->wide_string.value; - vchar.size = fmt_expr->wide_string.value.size; - vchar.first = wstring_first; - vchar.next = wstring_next; - vchar.is_digit = wstring_isdigit; - } else if (fmt_expr->kind == EXPR_STRING_LITERAL) { - vchar.string = &fmt_expr->string.value; - vchar.size = fmt_expr->string.value.size; - vchar.first = string_first; - vchar.next = string_next; - vchar.is_digit = string_isdigit; - } else { - return; + switch (fmt_expr->kind) { + case EXPR_STRING_LITERAL: + vchar.string = &fmt_expr->string.value; + vchar.size = fmt_expr->string.value.size; + vchar.first = string_first; + vchar.next = string_next; + vchar.is_digit = string_isdigit; + break; + + case EXPR_WIDE_STRING_LITERAL: + vchar.string = &fmt_expr->wide_string.value; + vchar.size = fmt_expr->wide_string.value.size; + vchar.first = wstring_first; + vchar.next = wstring_next; + vchar.is_digit = wstring_isdigit; + break; + + case EXPR_CONDITIONAL: { + conditional_expression_t const *const c = &fmt_expr->conditional; + expression_t const * t = c->true_expression; + if (t == NULL) + t = c->condition; + int const nt = internal_check_printf_format(t, arg, spec); + int const nf = internal_check_printf_format(c->false_expression, arg, spec); + return nt > nf ? nt : nf; + } + + default: + return -1; } - /* find the real args */ - for(; idx < spec->arg_idx; ++idx) - arg = arg->next; const source_position_t *pos = &fmt_expr->base.source_position; unsigned fmt = vchar.first(&vchar); @@ -215,7 +230,7 @@ static void check_printf_format(const call_argument_t *arg, const format_spec_t /* ... argument selector */ fmt_flags = FMT_FLAG_NONE; /* reset possibly set 0-flag */ /* TODO implement */ - return; + return -1; } /* ... minimum field width */ } else { @@ -257,7 +272,7 @@ break_fmt_flags: fmt = vchar.next(&vchar); if (arg == NULL) { warningf(pos, "missing argument for '*' field width in conversion specification %u", num_fmt); - return; + return -1; } const type_t *const arg_type = arg->expression->base.type; if (arg_type != type_int) { @@ -278,7 +293,7 @@ break_fmt_flags: fmt = vchar.next(&vchar); if (arg == NULL) { warningf(pos, "missing argument for '*' precision in conversion specification %u", num_fmt); - return; + return -1; } const type_t *const arg_type = arg->expression->base.type; if (arg_type != type_int) { @@ -515,27 +530,31 @@ eval_fmt_mod_unsigned: default: warningf(pos, "encountered unknown conversion specifier '%%%C' at position %u", (wint_t)fmt, num_fmt); + if (arg == NULL) { + warningf(pos, "too few arguments for format string"); + return -1; + } goto next_arg; } format_flags_t wrong_flags = fmt_flags & ~allowed_flags; if (wrong_flags != 0) { - char wrong[8]; - int idx = 0; - if (wrong_flags & FMT_FLAG_HASH) wrong[idx++] = '#'; - if (wrong_flags & FMT_FLAG_ZERO) wrong[idx++] = '0'; - if (wrong_flags & FMT_FLAG_MINUS) wrong[idx++] = '-'; - if (wrong_flags & FMT_FLAG_SPACE) wrong[idx++] = ' '; - if (wrong_flags & FMT_FLAG_PLUS) wrong[idx++] = '+'; - if (wrong_flags & FMT_FLAG_TICK) wrong[idx++] = '\''; - wrong[idx] = '\0'; + char wrong[8]; + char *p = wrong; + if (wrong_flags & FMT_FLAG_HASH) *p++ = '#'; + if (wrong_flags & FMT_FLAG_ZERO) *p++ = '0'; + if (wrong_flags & FMT_FLAG_MINUS) *p++ = '-'; + if (wrong_flags & FMT_FLAG_SPACE) *p++ = ' '; + if (wrong_flags & FMT_FLAG_PLUS) *p++ = '+'; + if (wrong_flags & FMT_FLAG_TICK) *p++ = '\''; + *p = '\0'; warningf(pos, "invalid format flags \"%s\" in conversion specification %%%c at position %u", wrong, fmt, num_fmt); } if (arg == NULL) { warningf(pos, "too few arguments for format string"); - return; + return -1; } { /* create a scope here to prevent warning about the jump to next_arg */ @@ -573,17 +592,42 @@ next_arg: arg = arg->next; } if (!atend(&vchar)) { - warningf(pos, "format string contains NUL"); + warningf(pos, "format string contains '\\0'"); } - if (arg != NULL) { - unsigned num_args = num_fmt; - while (arg != NULL) { - ++num_args; - arg = arg->next; - } - warningf(pos, "%u argument%s but only %u format string%s", + return num_fmt; +} + +/** + * Check printf-style format. + */ +static void check_printf_format(call_argument_t const *arg, format_spec_t const *const spec) +{ + /* find format arg */ + size_t idx = 0; + for (; idx < spec->fmt_idx; ++idx) { + if (arg == NULL) + return; + arg = arg->next; + } + + expression_t const *const fmt_expr = arg->expression; + + /* find the real args */ + for (; idx < spec->arg_idx && arg != NULL; ++idx) + arg = arg->next; + + int const num_fmt = internal_check_printf_format(fmt_expr, arg, spec); + if (num_fmt < 0) + return; + + size_t num_args = 0; + for (; arg != NULL; arg = arg->next) + ++num_args; + if (num_args > (size_t)num_fmt) { + warningf(&fmt_expr->base.source_position, + "%u argument%s but only %u format specifier%s", num_args, num_args != 1 ? "s" : "", - num_fmt, num_fmt != 1 ? "s" : ""); + num_fmt, num_fmt != 1 ? "s" : ""); } } @@ -594,8 +638,11 @@ static void check_scanf_format(const call_argument_t *arg, const format_spec_t * { /* find format arg */ unsigned idx = 0; - for (; idx < spec->fmt_idx; ++idx) + for (; idx < spec->fmt_idx; ++idx) { + if (arg == NULL) + return; arg = arg->next; + } const expression_t *fmt_expr = arg->expression; if (fmt_expr->kind == EXPR_UNARY_CAST_IMPLICIT) { @@ -619,7 +666,7 @@ static void check_scanf_format(const call_argument_t *arg, const format_spec_t * return; } /* find the real args */ - for (; idx < spec->arg_idx; ++idx) + for (; idx < spec->arg_idx && arg != NULL; ++idx) arg = arg->next; const source_position_t *pos = &fmt_expr->base.source_position; @@ -843,6 +890,10 @@ eval_fmt_mod_unsigned: default: warningf(pos, "encountered unknown conversion specifier '%%%C' at position %u", (wint_t)fmt, num_fmt); + if (arg == NULL) { + warningf(pos, "too few arguments for format string"); + return; + } goto next_arg; } @@ -888,7 +939,7 @@ next_arg: arg = arg->next; } if (!atend(&vchar)) { - warningf(pos, "format string contains NUL"); + warningf(pos, "format string contains '\\0'"); } if (arg != NULL) { unsigned num_args = num_fmt; @@ -896,7 +947,7 @@ next_arg: ++num_args; arg = arg->next; } - warningf(pos, "%u argument%s but only %u format string%s", + warningf(pos, "%u argument%s but only %u format specifier%s", num_args, num_args != 1 ? "s" : "", num_fmt, num_fmt != 1 ? "s" : ""); } @@ -971,7 +1022,7 @@ void check_format(const call_expression_t *const call) * This allows to check format even in MS mode or without header included. */ const char *const name = entity->base.symbol->string; - for (size_t i = 0; i < sizeof(builtin_table) / sizeof(builtin_table[0]); ++i) { + for (size_t i = 0; i < lengthof(builtin_table); ++i) { if (strcmp(name, builtin_table[i].name) == 0) { switch (builtin_table[i].fmt_kind) { case FORMAT_PRINTF: