* updated variadic printf/scanf checking
@ 2026-06-19 7:05 Ben Dooks
2026-06-19 7:05 ` [RFC v4 1/4] parse: initial parsing of __attribute__((format)) Ben Dooks
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Ben Dooks @ 2026-06-19 7:05 UTC (permalink / raw)
To: linux-sparse
I'm reposting this as an RFC as there has been quite a bit of work on
this to get scanf in and to try and simplify the code, fix a few bugs
found when checking the kernel. Depending on the reviews I will post
this for inclusion.
I am also looking at some work to try and make printf and scanf extensible
with the interesting types the kernel adds to printk, either by a new flag
or by some sort of attribute. This is not included in this set.
I think the tests need some more work on scanf, otherwise it seems to work
well with the kernel, and not really found any further issues other than
the odd signed/unsigned and type.
^ permalink raw reply [flat|nested] 5+ messages in thread
* [RFC v4 1/4] parse: initial parsing of __attribute__((format))
2026-06-19 7:05 updated variadic printf/scanf checking Ben Dooks
@ 2026-06-19 7:05 ` Ben Dooks
2026-06-19 7:05 ` [RFC v4 2/4] add -Wformat Ben Dooks
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Ben Dooks @ 2026-06-19 7:05 UTC (permalink / raw)
To: linux-sparse; +Cc: Ben Dooks
Add code to parse the __attribute__((format)) used to indicate that
a variadic function takes a printf-style format string and where
those are. Save the data in ctype ready for checking when such an
function is encoutered.
Signed-off-by: Ben Dooks <ben.dooks@codethink.co.uk>
--
v2:
- apply comments about arg names and early-exit from function
- remove the KW_UNUSED
v3:
- merged the scanf bits back in here as scanf is now inclided
---
parse.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
symbol.h | 17 +++++++++++--
2 files changed, 86 insertions(+), 3 deletions(-)
diff --git a/parse.c b/parse.c
index 9389079e..06818bd1 100644
--- a/parse.c
+++ b/parse.c
@@ -86,7 +86,7 @@ static attr_t
attribute_cleanup,
attribute_designated_init,
attribute_transparent_union, ignore_attribute,
- attribute_mode, attribute_force;
+ attribute_mode, attribute_force, attribute_format;
typedef struct symbol *to_mode_t(struct symbol *);
@@ -389,6 +389,10 @@ static struct symbol_op attr_force_op = {
.attribute = attribute_force,
};
+static struct symbol_op attr_format_op = {
+ .attribute = attribute_format,
+};
+
static struct symbol_op address_space_op = {
.attribute = attribute_address_space,
};
@@ -448,6 +452,16 @@ static struct symbol_op mode_word_op = {
.to_mode = to_word_mode
};
+static struct symbol_op attr_printf_op = {
+ .type = KW_FORMAT,
+ .class = FMT_PRINTF,
+};
+
+static struct symbol_op attr_scanf_op = {
+ .type = KW_FORMAT,
+ .class = FMT_SCANF,
+};
+
/*
* Define the keyword and their effects.
* The entries in the 'typedef' and put in NS_TYPEDEF and
@@ -565,6 +579,9 @@ static struct init_keyword {
D("pure", &attr_fun_op, .mods = MOD_PURE),
A("const", &attr_fun_op, .mods = MOD_PURE),
D("gnu_inline", &attr_fun_op, .mods = MOD_GNU_INLINE),
+ D("format", &attr_format_op),
+ D("printf", &attr_printf_op),
+ D("scanf", &attr_scanf_op),
/* Modes */
D("mode", &mode_op),
@@ -1235,6 +1252,57 @@ static struct token *attribute_address_space(struct token *token, struct symbol
return token;
}
+static int invalid_format_args(long long start, long long at)
+{
+ return start < 0 || at < 0 || start > USHRT_MAX || at > USHRT_MAX ||
+ (start == at && start > 0) ||
+ (start == 0 && at == 0);
+}
+
+static struct token *attribute_format(struct token *token, struct symbol *attr, struct decl_state *ctx)
+{
+ struct expression *arg_type, *arg_fmt, *arg_argpos;
+ struct symbol *fmt_sym = NULL;
+ long long start, at;
+
+ /* expecting format ( type, fmt, va_args at) */
+
+ token = expect(token, '(', "after format attribute");
+ if (token_type(token) == TOKEN_IDENT)
+ fmt_sym = lookup_keyword(token->ident, NS_KEYWORD);
+ if (fmt_sym && (!fmt_sym->op || fmt_sym->op->type != KW_FORMAT))
+ fmt_sym = NULL;
+
+ token = conditional_expression(token, &arg_type);
+ token = expect(token, ',', "format attribute type");
+ token = conditional_expression(token, &arg_fmt);
+ token = expect(token, ',', "format attribute type position");
+ token = conditional_expression(token, &arg_argpos);
+ token = expect(token, ')', "format attribute arg position");
+
+ if (!fmt_sym || !arg_type || !arg_fmt || !arg_argpos) {
+ warning(token->pos, "missing format attribute argument(s)");
+ return token;
+ }
+
+ start = get_expression_value(arg_argpos);
+ at = get_expression_value(arg_fmt);
+
+ if (invalid_format_args(start, at)) {
+ warning(token->pos, "bad format positions");
+ } else if (start == 0) {
+ /* nothing to do here, is va_list function */
+ } else if (start < at) {
+ warning(token->pos, "format cannot be after va_args");
+ } else {
+ ctx->ctype.format.index = at;
+ ctx->ctype.format.first = start;
+ ctx->ctype.format.type = fmt_sym->op->class;
+ }
+
+ return token;
+}
+
static struct symbol *to_QI_mode(struct symbol *ctype)
{
if (ctype->ctype.base_type != &int_type)
@@ -3025,6 +3093,8 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
if (!(decl->ctype.modifiers & MOD_STATIC))
decl->ctype.modifiers |= MOD_EXTERN;
+
+ base_type->ctype.format = decl->ctype.format;
} else if (base_type == &void_ctype && !(decl->ctype.modifiers & MOD_EXTERN)) {
sparse_error(token->pos, "void declaration");
}
diff --git a/symbol.h b/symbol.h
index 3552d439..296861b4 100644
--- a/symbol.h
+++ b/symbol.h
@@ -83,8 +83,8 @@ enum keyword {
KW_ASM = 1 << 5,
KW_MODE = 1 << 6,
KW_STATIC = 1 << 7,
- // KW UNUSED = 1 << 8,
- KW_EXACT = 1 << 9,
+ KW_EXACT = 1 << 8,
+ KW_FORMAT = 1 << 9,
};
struct context {
@@ -96,12 +96,25 @@ extern struct context *alloc_context(void);
DECLARE_PTR_LIST(context_list, struct context);
+/* the types of formatting from __attribute__((format)) */
+enum {
+ FMT_PRINTF = 1,
+ FMT_SCANF = 2,
+};
+
+struct attr_format {
+ unsigned short type;
+ unsigned short index; /* index in argument list for format string */
+ unsigned short first; /* where first variadic argument is */
+};
+
struct ctype {
struct symbol *base_type;
unsigned long modifiers;
unsigned long alignment;
struct context_list *contexts;
struct ident *as;
+ struct attr_format format;
};
struct decl_state {
--
2.37.2.352.g3c44437643
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [RFC v4 2/4] add -Wformat
2026-06-19 7:05 updated variadic printf/scanf checking Ben Dooks
2026-06-19 7:05 ` [RFC v4 1/4] parse: initial parsing of __attribute__((format)) Ben Dooks
@ 2026-06-19 7:05 ` Ben Dooks
2026-06-19 7:05 ` [RFC v4 3/4] evaluate: check variadic argument types against formatting info Ben Dooks
2026-06-19 7:05 ` [RFC v4 4/4] tests: add varargs printf format tests Ben Dooks
3 siblings, 0 replies; 5+ messages in thread
From: Ben Dooks @ 2026-06-19 7:05 UTC (permalink / raw)
To: linux-sparse; +Cc: Ben Dooks
Add option to enable/disable format checking (and default it to off)
Signed-off-by: Ben Dooks <ben.dooks@codethink.co.uk>
---
options.c | 2 ++
options.h | 1 +
sparse.1 | 8 ++++++++
3 files changed, 11 insertions(+)
diff --git a/options.c b/options.c
index 6ee4d878..54ac00b4 100644
--- a/options.c
+++ b/options.c
@@ -106,6 +106,7 @@ int Wflexible_array_array = 1;
int Wflexible_array_nested = 0;
int Wflexible_array_sizeof = 0;
int Wflexible_array_union = 0;
+int Wformat = 0;
int Wimplicit_int = 1;
int Winit_cstring = 0;
int Wint_to_pointer_cast = 1;
@@ -865,6 +866,7 @@ static const struct flag warnings[] = {
{ "flexible-array-nested", &Wflexible_array_nested },
{ "flexible-array-sizeof", &Wflexible_array_sizeof },
{ "flexible-array-union", &Wflexible_array_union },
+ { "format", &Wformat },
{ "implicit-int", &Wimplicit_int },
{ "init-cstring", &Winit_cstring },
{ "int-to-pointer-cast", &Wint_to_pointer_cast },
diff --git a/options.h b/options.h
index c2a9551a..105c45d0 100644
--- a/options.h
+++ b/options.h
@@ -106,6 +106,7 @@ extern int Wflexible_array_array;
extern int Wflexible_array_nested;
extern int Wflexible_array_sizeof;
extern int Wflexible_array_union;
+extern int Wformat;
extern int Wimplicit_int;
extern int Winit_cstring;
extern int Wint_to_pointer_cast;
diff --git a/sparse.1 b/sparse.1
index 2fba7e7a..64b0571e 100644
--- a/sparse.1
+++ b/sparse.1
@@ -285,6 +285,14 @@ To have any effect, at least one of \fB-Wflexible-array-array\fR,
be enabled.
Sparse does issue these warnings by default.
+.B \-Wformat
+Warn about parameter mismatch to any variadic function which specifies
+where the format string is specified with the
+.BI __attribute__((format( type, message, va_start )))
+attribute.
+
+Sparse does not issue these warnings by default. To turn them on, use
+\fB\-W-format\fR.
.
.TP
.B \-Winit\-cstring
--
2.37.2.352.g3c44437643
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [RFC v4 3/4] evaluate: check variadic argument types against formatting info
2026-06-19 7:05 updated variadic printf/scanf checking Ben Dooks
2026-06-19 7:05 ` [RFC v4 1/4] parse: initial parsing of __attribute__((format)) Ben Dooks
2026-06-19 7:05 ` [RFC v4 2/4] add -Wformat Ben Dooks
@ 2026-06-19 7:05 ` Ben Dooks
2026-06-19 7:05 ` [RFC v4 4/4] tests: add varargs printf format tests Ben Dooks
3 siblings, 0 replies; 5+ messages in thread
From: Ben Dooks @ 2026-06-19 7:05 UTC (permalink / raw)
To: linux-sparse; +Cc: Ben Dooks
The variadic argumebt code did not check any of the variadic arguments
as it did not previously know the possible type. Now we have the possible
formatting information stored in the ctype, we can do some checks on the
printf formatting types.
Signed-off-by: Ben Dooks <ben.dooks@codethink.co.uk>
--
V2:
- try and reduce size/if nesting of parse_printf_get_fmt()
- change return for parse_format_printf() and try and simplify
- reworked the test code to reduce numner of test functions
- bailed out early if verification isn't ok
V3:
- fixed issue with kernel testing
- fixed logic error in one check
V4:
- rewritten to deal with scanf
- made some of the format code common and tried to simplify
- fixed bugs found with checking linux-kernel#
Note, this is quite a re-write from the original so probably will
need re-reviewing
---
Makefile | 1 +
builtin.c | 4 +-
evaluate.c | 14 +-
evaluate.h | 10 +-
verify-format.c | 571 ++++++++++++++++++++++++++++++++++++++++++++++++
verify-format.h | 6 +
6 files changed, 599 insertions(+), 7 deletions(-)
create mode 100644 verify-format.c
create mode 100644 verify-format.h
diff --git a/Makefile b/Makefile
index e172758b..670e95aa 100644
--- a/Makefile
+++ b/Makefile
@@ -90,6 +90,7 @@ LIB_OBJS += tokenize.o
LIB_OBJS += unssa.o
LIB_OBJS += utils.o
LIB_OBJS += version.o
+LIB_OBJS += verify-format.o
PROGRAMS :=
PROGRAMS += compile
diff --git a/builtin.c b/builtin.c
index a704c5e8..80e9fa39 100644
--- a/builtin.c
+++ b/builtin.c
@@ -439,7 +439,7 @@ static int evaluate_generic_int_op(struct expression *expr)
NEXT_PTR_LIST(t);
} END_FOR_EACH_PTR(arg);
FINISH_PTR_LIST(t);
- return evaluate_arguments(types, expr->args);
+ return evaluate_arguments(NULL, types, expr->args);
err:
sparse_error(arg->pos, "non-integer type for argument %d:", n);
@@ -503,7 +503,7 @@ static int eval_atomic_common(struct expression *expr)
if (!expr->ctype) // set the return type, if needed
expr->ctype = ctype;
- return evaluate_arguments(types, expr->args);
+ return evaluate_arguments(NULL, types, expr->args);
err:
sparse_error(arg->pos, "invalid type for argument %d:", n);
diff --git a/evaluate.c b/evaluate.c
index 85a6447b..4303bae1 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -42,6 +42,7 @@
#include "symbol.h"
#include "target.h"
#include "expression.h"
+#include "verify-format.h"
struct symbol *current_fn;
@@ -1387,8 +1388,8 @@ static int whitelist_pointers(struct symbol *t1, struct symbol *t2)
return !Wtypesign;
}
-static int check_assignment_types(struct symbol *target, struct expression **rp,
- const char **typediff)
+int check_assignment_types(struct symbol *target, struct expression **rp,
+ const char **typediff)
{
struct symbol *source = degenerate(*rp);
struct symbol *t, *s;
@@ -2325,7 +2326,8 @@ static struct symbol *evaluate_alignof(struct expression *expr)
return size_t_ctype;
}
-int evaluate_arguments(struct symbol_list *argtypes, struct expression_list *head)
+int evaluate_arguments(struct symbol *fn, struct symbol_list *argtypes,
+ struct expression_list *head)
{
struct expression *expr;
struct symbol *argtype;
@@ -2366,6 +2368,10 @@ int evaluate_arguments(struct symbol_list *argtypes, struct expression_list *hea
NEXT_PTR_LIST(argtype);
} END_FOR_EACH_PTR(expr);
FINISH_PTR_LIST(argtype);
+
+ if (fn && Wformat)
+ verify_format_attribute(fn, head);
+
return 1;
}
@@ -3192,7 +3198,7 @@ static struct symbol *evaluate_call(struct expression *expr)
if (!sym->op->args(expr))
return NULL;
} else {
- if (!evaluate_arguments(ctype->arguments, arglist))
+ if (!evaluate_arguments(ctype, ctype->arguments, arglist))
return NULL;
args = expression_list_size(expr->args);
fnargs = symbol_list_size(ctype->arguments);
diff --git a/evaluate.h b/evaluate.h
index a16e9703..3f51129d 100644
--- a/evaluate.h
+++ b/evaluate.h
@@ -28,8 +28,16 @@ void evaluate_symbol_list(struct symbol_list *list);
///
// evaluate the arguments of a function
+// @fn: the symbol of the prototype
// @argtypes: the list of the types in the prototype
// @args: the list of the effective arguments
-int evaluate_arguments(struct symbol_list *argtypes, struct expression_list *args);
+int evaluate_arguments(struct symbol *fn, struct symbol_list *argtypes, struct expression_list *args);
+///
+// check if assignment types are compatible
+// @target: the target assignment
+// @rp: the expression
+// @typediff: the resulant message if different type
+int check_assignment_types(struct symbol *target, struct expression **rp,
+ const char **typediff);
#endif
diff --git a/verify-format.c b/verify-format.c
new file mode 100644
index 00000000..02238dd0
--- /dev/null
+++ b/verify-format.c
@@ -0,0 +1,571 @@
+/*
+ * sparse/verify-format.c
+ *
+ * Copyright (C) 2019,2026 Codethink Ltd.
+ * Written by Ben Dooks <ben.dooks@codethink.co.uk>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * Verification code for format-attributes (printf, scanf)
+ */
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include "evaluate.h"
+#include "lib.h"
+#include "allocate.h"
+#include "parse.h"
+#include "token.h"
+#include "symbol.h"
+#include "target.h"
+#include "expression.h"
+#include "verify-format.h"
+
+struct format_type {
+ const char *format;
+ struct symbol *data;
+
+ /* if ptr_deref and it is a pointer type, we do care about
+ * any address space differences, otherwise just ignore
+ */
+ bool ptr_deref;
+};
+
+struct format_state {
+ struct expression *fmt_expr;
+ struct expression *pos_expr;
+ struct format_type ftype;
+ struct attr_format_ext_type *ext_types;
+ unsigned int first;
+ unsigned int position;
+ unsigned int arg_index;
+ unsigned int used_position: 1;
+};
+
+static struct expression *get_nth_expression(struct expression_list *args, int nr)
+{
+ return ptr_list_nth_entry((struct ptr_list *)args, nr);
+}
+
+/* parser for general scanf/printf format bits
+ *
+ * note, only roughly in order of the initial letters
+ */
+static int parse_general_fmt(struct format_type *type,
+ bool is_scanf,
+ const char *msg,
+ const char **msgout)
+{
+ const char *ptr = msg;
+ int szmod = 0;
+
+ type->ptr_deref = false;
+ *msgout = ptr;
+
+retry:
+ switch (*ptr++) {
+ case 'c':
+ type->data = &char_ctype;
+ break;
+ case 'i':
+ case 'd':
+ switch (szmod) {
+ case -2:
+ type->data = &char_ctype;
+ break;
+ case -1:
+ type->data = &short_ctype;
+ break;
+ case 0:
+ type->data = &int_ctype;
+ break;
+ case 1:
+ type->data = &long_ctype;
+ break;
+ case 2:
+ type->data = &llong_ctype;
+ break;
+ case 3:
+ type->data = intmax_ctype;
+ break;
+ }
+ break;
+ case 'f':
+ case 'F':
+ case 'g':
+ case 'G':
+ type->data = szmod == 1 ? &ldouble_ctype : &double_ctype;
+ break;
+ case 'h':
+ szmod = -1;
+ if (*ptr == 'h') { // promotion from char
+ szmod = -2;
+ ptr++;
+ }
+ goto retry;
+ case 'L':
+ szmod = 1;
+ goto retry;
+ case 'l':
+ szmod++;
+ goto retry;
+ case 't':
+ szmod = 2;
+ goto retry;
+ case 'j':
+ szmod = 3;
+ goto retry;
+ case 'p':
+ /* check for pointer being printed as hex explicitly */
+
+ type->data = is_scanf ? &ptr_ctype : &const_ptr_ctype;
+ if (*ptr == 'x' || *ptr == 'X') {
+ ptr++;
+ } else if (isalpha(*ptr)) {
+ /* probably some extra specifiers after %p or some
+ * linux kernel extension */
+ type->ptr_deref = true;
+ ptr++;
+ }
+ break;
+ case 's':
+ /* note, %ls is wchar, not char and does this work with sscanf? */
+ type->data = is_scanf ? &string_ctype : &const_string_ctype;
+ type->ptr_deref = true;
+ break;
+ case 'n':
+ /* pointer to an interger to write count into */
+ type->data = is_scanf ? &int_ctype : intptr_ctype;
+ type->ptr_deref = true;
+ break;
+ case 'x':
+ case 'X':
+ case 'u':
+ case 'o':
+ switch (szmod) {
+ case -2:
+ type->data = &uchar_ctype;
+ break;
+ case -1:
+ type->data = &ushort_ctype;
+ break;
+ case 0:
+ type->data = &uint_ctype;
+ break;
+ case 1:
+ type->data = &ulong_ctype;
+ break;
+ case 2:
+ type->data = &ullong_ctype;
+ break;
+ case 3:
+ type->data = uintmax_ctype;
+ break;
+ }
+ break;
+ case 'z':
+ if (*ptr == 'd' || *ptr == 'i') {
+ ptr++;
+ type->data = ssize_t_ctype;
+ } else if (*ptr == 'u' || *ptr == 'x' || *ptr == 'X' ||
+ *ptr == 'o') {
+ ptr++;
+ type->data = size_t_ctype;
+ }
+ break;
+ default:
+ return 0;
+ }
+
+ *msgout = ptr;
+ return 1;
+}
+
+// msgout is end of parsed format msg
+static int parse_printf_get_fmt(struct format_type *type,
+ const char *msg,
+ const char **msgout)
+{
+ int ret = parse_general_fmt(type, false, msg, msgout);
+ const char *ptr = msg;
+
+ if (ret > 0)
+ return ret;
+
+ // %w is a special case for printf.
+
+ *msgout = ptr;
+ return ret;
+}
+
+static int is_printf_flag(char ch)
+{
+ return ch == '0' || ch == '+' || ch == '-' || ch == ' ' || ch == '#';
+}
+
+static int printf_parse_position(const char **fmt)
+{
+ const char *ptr= *fmt;
+
+ if (!isdigit(*ptr))
+ return -1;
+ while (isdigit(*ptr))
+ ptr++;
+ if (*ptr == '$') {
+ const char *pos = *fmt;
+ *fmt = ptr+1;
+ return strtoul(pos, NULL, 10);
+ }
+ return -1;
+}
+
+static void parse_format_printf_checkpos(struct format_state *state,
+ const char *which)
+{
+ if (state->used_position) {
+ warning(state->fmt_expr->pos,
+ "format %d: %s: explicit position needed",
+ state->arg_index, which);
+ }
+}
+
+static int parse_format_printf_argfield(const char **fmtptr,
+ struct format_state *state,
+ struct expression_list *args,
+ const char *which)
+{
+ struct expression *expr;
+ const char *fmt = *fmtptr;
+ const char *typediff;
+ int argpos = -1;
+
+ /* check for simple digit-string width/precision specifier first */
+ if (*fmt != '*') {
+ while (isdigit(*fmt))
+ fmt++;
+ *fmtptr = fmt;
+ return 0;
+ }
+
+ fmt++;
+ argpos = printf_parse_position(&fmt);
+
+ if (argpos > 0) {
+ argpos += state->first - 1;
+ state->used_position = 1;
+ } else {
+ argpos = state->arg_index++;
+ parse_format_printf_checkpos(state, which);
+ }
+
+ *fmtptr = fmt;
+ expr = get_nth_expression(args, argpos-1);
+ if (!expr) {
+ warning(state->fmt_expr->pos, "%s: no argument at position %d",
+ which, argpos);
+ return 1;
+ }
+
+ /* check the value we got was int/uint type */
+
+ if (check_assignment_types(&int_ctype, &expr, &typediff) == 0) {
+ warning(expr->pos, "incorrect type for %s argument %d", which, argpos);
+ info(expr->pos, " expected %s", show_typename(&int_ctype));
+ info(expr->pos, " got %s", show_typename(expr->ctype));
+ }
+
+ return 0;
+}
+
+static int parse_scanf_get_fmt(struct format_type *type,
+ const char *msg,
+ const char **msgout)
+{
+ int ret = parse_general_fmt(type, true, msg, msgout);
+ static struct symbol scanf_type;
+
+ /* if the general format found something, then we probably need
+ * to change it to being a pointer to the type we found.
+ */
+ if (ret > 0) {
+ if (type->data != &string_ctype) {
+ scanf_type.ctype.base_type = type->data;
+ scanf_type.type = SYM_PTR;
+ type->data = &scanf_type;
+ }
+
+ return ret;
+ }
+
+ // todo - check for anything scanf specific here
+ return ret;
+}
+
+static const char *parse_scanf_skip_modifiers(const char *ptr)
+{
+ while (*ptr == 'm' || *ptr == '\'' || isdigit(*ptr))
+ ptr++;
+ return ptr;
+}
+
+static int parse_format_scanf(const char **fmtstring,
+ struct format_state *state,
+ struct expression_list *args)
+{
+ struct format_type *ftype = &state->ftype;
+ struct expression *expr;
+ const char *fmt = *fmtstring; /* pointer to parse position */
+ const char *fmtpost = NULL; /* moved to end of the parsed format */
+ int pos = state->arg_index; /* position of the argument */
+ int ret;
+
+ /* trivial check for %% */
+ fmt++;
+ if (fmt[0] == '%') {
+ *fmtstring = fmt+1;
+ return 0;
+ }
+
+ /* this is discarded input, so just ignore */
+ if (fmt[0] == '*') {
+ *fmtstring = fmt+1;
+ return 0;
+ }
+
+ pos = printf_parse_position(&fmt);
+ if (pos > 0) {
+ state->used_position = 1;
+ pos += state->first - 1;
+ } else {
+ parse_format_printf_checkpos(state, "position");
+ pos = state->arg_index++;
+ }
+ state->position = pos;
+
+ fmt = parse_scanf_skip_modifiers(fmt);
+ *fmtstring = fmt;
+
+ ret = parse_scanf_get_fmt(ftype, fmt, &fmtpost);
+ if (!ret) {
+ /* assume it's a single character we just don't know about
+ * such as a linux-kernel extended format.
+ */
+ fmtpost = *fmtstring + 2;
+ warning(state->fmt_expr->pos, "cannot evaluate type '%.*s'",
+ (int)(fmtpost - *fmtstring), *fmtstring);
+ *fmtstring += 1;
+ return -1;
+ }
+
+ *fmtstring = fmtpost;
+ expr = get_nth_expression(args, pos-1);
+ if (!expr) {
+ /* no argument, but otherwise valid argument string */
+ warning(state->fmt_expr->pos, "no argument at position '%d'", pos);
+ return 0;
+ }
+
+ state->pos_expr = expr;
+ return 1;
+}
+
+/*
+ * printf format parsing code
+ *
+ * this code currently does not:
+ * - check castable types (such as int vs long vs long long)
+ * - validate all arguments specified are also used...
+ *
+ * positional arguments make this more difficult as they get consumed first if
+ * no explicit position is used. If the position is explicilty stated then that
+ * is the first int$ argument and the width and precision fields must also be
+ * explicity defined.
+ */
+static int parse_format_printf(const char **fmtstring,
+ struct format_state *state,
+ struct expression_list *args)
+{
+ struct format_type *ftype = &state->ftype;
+ struct expression *expr;
+ const char *fmt = *fmtstring; /* pointer to parse position */
+ const char *fmtpost = NULL; /* moved to end of the parsed format */
+ int pos; /* position of the argument */
+ int ret;
+
+ /* trivial check for %% */
+ fmt++;
+ if (fmt[0] == '%') {
+ *fmtstring = fmt+1;
+ return 0;
+ }
+
+ /* if there's an explicit position, then work out where it is and
+ * mark it as this will go before the width/precision.
+ */
+ pos = printf_parse_position(&fmt);
+ if (pos > 0) {
+ state->used_position = 1;
+ pos += state->first - 1;
+ }
+
+ /* get rid of any formatting flag bits */
+ while (is_printf_flag(*fmt))
+ fmt++;
+
+ /* now there is the posibility of a width specifier */
+ if (parse_format_printf_argfield(&fmt, state, args, "width"))
+ return -1;
+
+ /* now we might have the precision specifier */
+ if (*fmt == '.') {
+ fmt++;
+ if (parse_format_printf_argfield(&fmt, state, args, "precision"))
+ return -1;
+ }
+
+ if (pos < 0) {
+ parse_format_printf_checkpos(state, "position");
+ pos = state->arg_index++;
+ }
+ state->position = pos;
+
+ ret = parse_printf_get_fmt(ftype, fmt, &fmtpost);
+ if (!ret) {
+ /* assume it's a single character we just don't know about
+ * such as a linux-kernel extended format.
+ */
+ fmtpost = *fmtstring + 2;
+ warning(state->fmt_expr->pos, "cannot evaluate type '%.*s'",
+ (int)(fmtpost - *fmtstring), *fmtstring);
+ *fmtstring += 1;
+ return -1;
+ }
+
+ *fmtstring = fmtpost;
+ expr = get_nth_expression(args, pos-1);
+ if (!expr) {
+ /* no argument, but otherwise valid argument string */
+ warning(state->fmt_expr->pos, "no argument at position '%d'", pos);
+ return 0;
+ }
+
+ state->pos_expr = expr;
+ return 1;
+}
+
+/*
+ * attempt to run through a printf format string and work out the types
+ * it specifies. The format is parsed from the __attribute__(format())
+ * in the parser code which stores the positions of the message and arg
+ * start in the ctype.
+ */
+void verify_format_attribute(struct symbol *fn, struct expression_list *args)
+{
+ struct format_state state = { };
+ struct expression *expr;
+ struct expression *init;
+ const char *string, *fmt_string;
+ int ret, fail = 0;
+
+ if (!fn->ctype.format.index)
+ return;
+
+ expr = get_nth_expression(args, fn->ctype.format.index-1);
+ if (!expr)
+ return;
+
+ if (expr->type != EXPR_SYMBOL || expr->symbol->ident)
+ return; // not a literal
+
+ init = expr->symbol->initializer;
+ if (!init || init->type != EXPR_STRING) {
+ warning(expr->pos, "not a format string");
+ return; // not a string
+ }
+ fmt_string = init->string->data;
+
+ state.fmt_expr = expr;
+ state.first = fn->ctype.format.first;
+ state.arg_index = fn->ctype.format.first;
+
+ if (!fmt_string) {
+ warning(expr->pos, "not a format string?");
+ return;
+ }
+
+ string = fmt_string;
+ while (string[0]) {
+ const char *typediff = "different types";
+
+ if (string[0] != '%') {
+ /* strip anything before the '%' */
+ string++;
+ continue;
+ }
+
+ switch (fn->ctype.format.type) {
+ case FMT_PRINTF:
+ ret = parse_format_printf(&string, &state, args);
+ break;
+ case FMT_SCANF:
+ ret = parse_format_scanf(&string, &state, args);
+ break;
+ default:
+ warning(expr->pos, "not printf or scanf format");
+ string++;
+ ret = -1;
+ }
+
+ if (ret < 0) {
+ fail++;
+ continue;
+ } else if (ret == 0)
+ continue;
+
+ if (!state.ftype.data) {
+ warning(expr->pos, "got here with no type data");
+ continue;
+ }
+
+ ret = check_assignment_types(state.ftype.data, &state.pos_expr, &typediff);
+ if (ret == 0 && !state.ftype.ptr_deref) {
+ /* if just printing, ignore address-space mismatches */
+
+ if (strcmp(typediff, "different address spaces") == 0)
+ ret = 1;
+ }
+
+ if (ret == 0) {
+ warning(expr->pos, "incorrect type in argument %d (%s)", state.position, typediff);
+ info(expr->pos, " expected %s", show_typename(state.ftype.data));
+ info(expr->pos, " got %s", show_typename(state.pos_expr->ctype));
+ }
+ }
+
+ if (fail > 0)
+ /* format string may have '\n' etc embedded in it */
+ warning(expr->pos, "cannot evaluate format string");
+}
diff --git a/verify-format.h b/verify-format.h
new file mode 100644
index 00000000..4a7ef79d
--- /dev/null
+++ b/verify-format.h
@@ -0,0 +1,6 @@
+#ifndef VERIFY_FORMAT_H
+#define VERIFY_FORMAT_H
+
+void verify_format_attribute(struct symbol *fn, struct expression_list *args);
+
+#endif
--
2.37.2.352.g3c44437643
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [RFC v4 4/4] tests: add varargs printf format tests
2026-06-19 7:05 updated variadic printf/scanf checking Ben Dooks
` (2 preceding siblings ...)
2026-06-19 7:05 ` [RFC v4 3/4] evaluate: check variadic argument types against formatting info Ben Dooks
@ 2026-06-19 7:05 ` Ben Dooks
3 siblings, 0 replies; 5+ messages in thread
From: Ben Dooks @ 2026-06-19 7:05 UTC (permalink / raw)
To: linux-sparse; +Cc: Ben Dooks
Add some tests for the new printf format checking code.
Note, these do not all pass yet.
Signed-off-by: Ben Dooks <ben.dooks@codethink.co.uk>
---
validation/varargs-format-addrspace1.c | 36 ++++++++
validation/varargs-format-bad.c | 18 ++++
validation/varargs-format-checking.c | 21 +++++
validation/varargs-format-position.c | 32 +++++++
validation/varargs-format-prefix.c | 19 ++++
validation/varargs-format-tests.c | 82 +++++++++++++++++
validation/varargs-type-formattest.c | 117 +++++++++++++++++++++++++
7 files changed, 325 insertions(+)
create mode 100644 validation/varargs-format-addrspace1.c
create mode 100644 validation/varargs-format-bad.c
create mode 100644 validation/varargs-format-checking.c
create mode 100644 validation/varargs-format-position.c
create mode 100644 validation/varargs-format-prefix.c
create mode 100644 validation/varargs-format-tests.c
create mode 100644 validation/varargs-type-formattest.c
diff --git a/validation/varargs-format-addrspace1.c b/validation/varargs-format-addrspace1.c
new file mode 100644
index 00000000..3370ac67
--- /dev/null
+++ b/validation/varargs-format-addrspace1.c
@@ -0,0 +1,36 @@
+
+extern int variadic(char *msg, ...) __attribute__((format (printf, 1, 2)));
+extern int variadic2(char *msg, int , ...) __attribute__((format (printf, 1, 3)));
+extern int variadic3(int, char *msg, ...) __attribute__((format (printf, 2, 3)));
+
+static void test(void) {
+ void __attribute__((noderef, address_space(1))) *a;
+ void *b;
+
+ variadic("%s\n", a);
+ variadic("%s\n", b);
+ variadic("%s %s\n", b, a);
+ variadic2("%s %s\n", 1, b, a);
+ variadic3(1, "%s %s\n", b, a);
+ variadic3(1, "%s %p\n", b, a);
+}
+
+/*
+ * check-name: variadic formatting test with address-space to %s
+ * check-command: sparse -Wformat $file
+ *
+ * check-error-start
+varargs-format-addrspace1.c:10:26: warning: incorrect type in argument 2 (different address spaces)
+varargs-format-addrspace1.c:10:26: expected char const *
+varargs-format-addrspace1.c:10:26: got void [noderef] <asn:1> *a
+varargs-format-addrspace1.c:12:32: warning: incorrect type in argument 3 (different address spaces)
+varargs-format-addrspace1.c:12:32: expected char const *
+varargs-format-addrspace1.c:12:32: got void [noderef] <asn:1> *a
+varargs-format-addrspace1.c:13:36: warning: incorrect type in argument 4 (different address spaces)
+varargs-format-addrspace1.c:13:36: expected char const *
+varargs-format-addrspace1.c:13:36: got void [noderef] <asn:1> *a
+varargs-format-addrspace1.c:14:36: warning: incorrect type in argument 4 (different address spaces)
+varargs-format-addrspace1.c:14:36: expected char const *
+varargs-format-addrspace1.c:14:36: got void [noderef] <asn:1> *a
+ * check-error-end
+ */
diff --git a/validation/varargs-format-bad.c b/validation/varargs-format-bad.c
new file mode 100644
index 00000000..82ae357c
--- /dev/null
+++ b/validation/varargs-format-bad.c
@@ -0,0 +1,18 @@
+
+extern int variadic(char *msg, ...) __attribute__((format (printf, 0, 0)));
+extern int variadic2(char *msg, int , ...) __attribute__((format (printf, 2, 2)));
+extern int variadic3(char *msg, int , ...) __attribute__((format (printf, 2, 1)));
+
+static void test(void) {
+}
+
+/*
+ * check-name: variadic formatting test with bad formatting parameters
+ * check-command: sparse -Wformat $file
+ *
+ * check-error-start
+varargs-format-bad.c:2:73: warning: bad format positions
+varargs-format-bad.c:3:80: warning: bad format positions
+varargs-format-bad.c:4:80: warning: format cannot be after va_args
+* check-error-end
+ */
diff --git a/validation/varargs-format-checking.c b/validation/varargs-format-checking.c
new file mode 100644
index 00000000..9f3e5ac2
--- /dev/null
+++ b/validation/varargs-format-checking.c
@@ -0,0 +1,21 @@
+
+extern void pf(char *msg, ...) __attribute__((format (printf, 1, 2)));
+
+static void test(void) {
+ pf("%u %lu %llu\n", 1U, 1UL, 1ULL);
+ pf("%d %ld %lld\n", 1, 1L, 1LL);
+ pf("%x %lx %llx\n", 1U, 1UL, 1ULL);
+ pf("%d %ld %lld\n", 1, 1L, 1L);
+}
+
+/*
+ * check-name: variadic formatting test type checking
+ * check-command: sparse -Wformat $file
+ * check-known-to-fail
+ *
+ * check-error-start
+varargs-format-checking.c:8:36: warning: incorrect type in argument 4 (different types)
+varargs-format-checking.c:8:36: expected long long
+varargs-format-checking.c:8:36: got long
+ * check-error-end
+ */
diff --git a/validation/varargs-format-position.c b/validation/varargs-format-position.c
new file mode 100644
index 00000000..88a4dbc2
--- /dev/null
+++ b/validation/varargs-format-position.c
@@ -0,0 +1,32 @@
+
+extern void pf(char *msg, ...) __attribute__((format (printf, 1, 2)));
+
+static void test(void) {
+ pf("%2$d %u\n", 1U, 1L);
+ pf("%3$d %2$u\n", 1U, 1);
+ pf("%1$d %2$d\n", 1L, 1);
+}
+
+/*
+ * check-name: variadic formatting test position checking
+ * check-command: sparse -Wformat $file
+ * check-known-to-fail
+ *
+ * check-error-start
+varargs-format-position.c:5:29: warning: incorrect type in argument 3 (different types)
+varargs-format-position.c:5:29: expected int
+varargs-format-position.c:5:29: got long
+varargs-format-position.c:5:12: warning: format 3: position: no position specified
+varargs-format-position.c:5:29: warning: incorrect type in argument 3 (different types)
+varargs-format-position.c:5:29: expected unsigned int
+varargs-format-position.c:5:29: got long
+varargs-format-position.c:6:12: warning: no argument at position '4'
+varargs-format-position.c:6:31: warning: incorrect type in argument 3 (different types)
+varargs-format-position.c:6:31: expected unsigned int
+varargs-format-position.c:6:31: got int
+varargs-format-position.c:7:27: warning: incorrect type in argument 2 (different types)
+varargs-format-position.c:7:27: expected int
+varargs-format-position.c:7:27: got long
+ * check-error-end
+ *
+ */
diff --git a/validation/varargs-format-prefix.c b/validation/varargs-format-prefix.c
new file mode 100644
index 00000000..8e2456e6
--- /dev/null
+++ b/validation/varargs-format-prefix.c
@@ -0,0 +1,19 @@
+
+extern int __attribute__((format (printf, 1, 2))) variadic(char *msg, ...);
+
+static int test(void) {
+ void __attribute__((noderef, address_space(1))) *a;
+
+ variadic("%s\n", a);
+}
+
+/*
+ * check-name: variadic formatting test prefix based __attribute__
+ * check-command: sparse -Wformat $file
+ *
+ * check-error-start
+varargs-format-prefix.c:7:26: warning: incorrect type in argument 2 (different address spaces)
+varargs-format-prefix.c:7:26: expected char const *
+varargs-format-prefix.c:7:26: got void [noderef] <asn:1> *a
+ * check-error-end
+ */
diff --git a/validation/varargs-format-tests.c b/validation/varargs-format-tests.c
new file mode 100644
index 00000000..9a5cb9cd
--- /dev/null
+++ b/validation/varargs-format-tests.c
@@ -0,0 +1,82 @@
+
+
+extern void pf(char *msg, ...) __attribute__((format (printf, 1, 2)));
+
+static int test(void)
+{
+ pf("%*d\n", 5, 10); /* value 10, print width is 5 */
+ pf("%2$*1$d\n", 5, 10); /* value 10, print width is 5 */
+ pf("%3$*2$d\n", 1, 5, 10); /* ok, skipping the '1' */
+ pf("%3$-*2$d\n", 1, 5, 10); /* ok, skipping the '1' */
+ pf("%3$*2$-d\n", 1, 5, 10); /* bad, the "-" shouldn't be before the 'd' */
+ pf("%3$ *2$d\n", 1, 5, 10); /* ok, skipping the '1' */
+ pf("%3$+*2$d\n", 1, 5, 10); /* ok, skipping the '1' */
+ pf("%3$0+*2$d\n", 1, 5, 10); /* ok, skipping the '1' */
+ pf("%3$+0*2$d\n", 1, 5, 10); /* ok, skipping the '1' */
+ pf("%3$+#*2$d\n", 1, 5, 10); /* ok, skipping the '1' */
+ pf("%3$+#*2$.5d\n", 1, 5, 10); /* ok, skipping the '1' */
+
+ /* go with some precision as well as width strings */
+ pf("%2$+*1$.6d\n", 5, 10); /* ok */
+ pf("%2$+*1$.*3$d\n", 5, 10, 6); /* ok */
+ pf("%2$+*3$.*1$d\n", 6, 10, 5); /* ok */
+ pf("%2$+*1$.*d\n", 5, 10, 6); /* not ok */
+
+ pf("%s", "msg");
+ return 0;
+}
+
+static void test2(int x, int y, const void *p)
+{
+ pf("%02x%02x %8p\n", x, y, p);
+}
+
+static inline void fn(int x) { pf("%08x\n", x); }
+static void test3(int x)
+{
+ fn;
+ fn(x);
+}
+
+static void test4(int i, unsigned int u)
+{
+ pf("%d\n", i);
+ pf("%x\n", u);
+}
+
+extern void sf(char *fmt, ...) __attribute__((format (scanf, 1, 2)));
+
+static void test5(void)
+{
+ char str[32];
+ char *s;
+ void *ptr;
+ void * const cp;
+ unsigned int u;
+ int i;
+
+ sf("%s %p %i %u", str, &ptr, &i, &u);
+ sf("%p", &cp);
+}
+
+static void test6(const char *str, int width)
+{
+ pf("%*s", width, str);
+ pf("%2$*1$d", width, str); /* equivalent to previous */
+ pf("%*s", str, width);
+}
+
+
+/*
+ * check-name: variadic formatting tests for width/precisions
+ * check-command: sparse -Wformat $file
+ *
+ * check-error-start
+varargs-format-tests.c:11:12: warning: cannot evaluate type '%3$*2$-d'
+varargs-format-tests.c:11:12: warning: cannot evaluate format string
+varargs-format-tests.c:22:12: warning: format 3: position: no position specified
+varargs-format-tests.c:59:13: warning: incorrect type in argument 2 (different modifiers)
+varargs-format-tests.c:59:13: expected void **
+varargs-format-tests.c:59:13: got void *const *
+ * check-error-end
+ */
diff --git a/validation/varargs-type-formattest.c b/validation/varargs-type-formattest.c
new file mode 100644
index 00000000..f01c6d89
--- /dev/null
+++ b/validation/varargs-type-formattest.c
@@ -0,0 +1,117 @@
+
+extern void pf1(char *msg, ...) __attribute__((format (printf, 1, 2)));
+extern void pf2(int m, char *msg, ...) __attribute__((format (printf, 2, 3)));
+
+/* run all the tests with both of these printf formatted types */
+#define pf(x...) do { pf1(x); pf2(1, x); } while(0);
+
+static void test(void) {
+ /* first two are valid */
+ pf("%*d", 5, 10); /* value 10, print width is 5 */
+ pf("%2$*1$d", 5, 10); /* value 10, print width is 5 */
+ pf("%2$*3$d", 5, 10); /* value 10, print width is ?? */
+
+ pf("%*d", 5, 10); /* value 10, print width is 5 */
+ pf("%*d", 5, 10L); /* value 10, print width is 5 (bad type) */
+ pf("%*d", 5UL, 10L); /* value 10, print width is 5 (bad type) */
+
+ pf("%3$*2$d", 1, 5, 10); /* ok, skipping the '1' */
+ pf("%3$*2$d", 1, 5, 10L); /* bad print type */
+ pf("%2$*3$d", 1UL, 10, 5); /* ok, try with swapping width/val */
+ pf("%2$*3$d", 1UL, 10L, 5); /* bad, try with swapping width/val */
+
+ /* and now try with precision specifiers */
+
+ pf("%*.6d", 5, 10); /* value 10, print width is 5 */
+ pf("%*.6d", 5, 10L); /* value 10, print width is 5 (bad type) */
+ pf("%*.6d", 5UL, 10L); /* value 10, print width is 5 (bad type) */
+
+ pf("%*.*d", 5, 6, 10); /* value 10, print width is 5 */
+ pf("%*.*d", 5, 6, 10L); /* value 10, print width is 5 (bad type) */
+ pf("%*.*d", 5UL, 6, 10L); /* value 10, print width is 5 (bad type) */
+ pf("%*.*d", 5, 6UL, 10); /* value 10, print width is 5 (bad type) */
+}
+
+/*
+ * check-name: variadic formatting test position checking types
+ * check-command: sparse -Wformat $file
+ * check-known-to-fail
+ *
+ * check-error-start
+varargs-type-formattest.c:12:9: warning: width: no argument at position 4
+varargs-type-formattest.c:12:9: warning: width: no argument at position 5
+varargs-type-formattest.c:15:9: warning: incorrect type in argument 3 (different types)
+varargs-type-formattest.c:15:9: expected int
+varargs-type-formattest.c:15:9: got long
+varargs-type-formattest.c:15:9: warning: incorrect type in argument 4 (different types)
+varargs-type-formattest.c:15:9: expected int
+varargs-type-formattest.c:15:9: got long
+varargs-type-formattest.c:16:9: warning: incorrect type for width argument 2
+varargs-type-formattest.c:16:9: expected int
+varargs-type-formattest.c:16:9: got unsigned long
+varargs-type-formattest.c:16:9: warning: incorrect type in argument 3 (different types)
+varargs-type-formattest.c:16:9: expected int
+varargs-type-formattest.c:16:9: got long
+varargs-type-formattest.c:16:9: warning: incorrect type for width argument 3
+varargs-type-formattest.c:16:9: expected int
+varargs-type-formattest.c:16:9: got unsigned long
+varargs-type-formattest.c:16:9: warning: incorrect type in argument 4 (different types)
+varargs-type-formattest.c:16:9: expected int
+varargs-type-formattest.c:16:9: got long
+varargs-type-formattest.c:19:9: warning: incorrect type in argument 4 (different types)
+varargs-type-formattest.c:19:9: expected int
+varargs-type-formattest.c:19:9: got long
+varargs-type-formattest.c:19:9: warning: incorrect type in argument 5 (different types)
+varargs-type-formattest.c:19:9: expected int
+varargs-type-formattest.c:19:9: got long
+varargs-type-formattest.c:21:9: warning: incorrect type in argument 3 (different types)
+varargs-type-formattest.c:21:9: expected int
+varargs-type-formattest.c:21:9: got long
+varargs-type-formattest.c:21:9: warning: incorrect type in argument 4 (different types)
+varargs-type-formattest.c:21:9: expected int
+varargs-type-formattest.c:21:9: got long
+varargs-type-formattest.c:26:9: warning: incorrect type in argument 3 (different types)
+varargs-type-formattest.c:26:9: expected int
+varargs-type-formattest.c:26:9: got long
+varargs-type-formattest.c:26:9: warning: incorrect type in argument 4 (different types)
+varargs-type-formattest.c:26:9: expected int
+varargs-type-formattest.c:26:9: got long
+varargs-type-formattest.c:27:9: warning: incorrect type for width argument 2
+varargs-type-formattest.c:27:9: expected int
+varargs-type-formattest.c:27:9: got unsigned long
+varargs-type-formattest.c:27:9: warning: incorrect type in argument 3 (different types)
+varargs-type-formattest.c:27:9: expected int
+varargs-type-formattest.c:27:9: got long
+varargs-type-formattest.c:27:9: warning: incorrect type for width argument 3
+varargs-type-formattest.c:27:9: expected int
+varargs-type-formattest.c:27:9: got unsigned long
+varargs-type-formattest.c:27:9: warning: incorrect type in argument 4 (different types)
+varargs-type-formattest.c:27:9: expected int
+varargs-type-formattest.c:27:9: got long
+varargs-type-formattest.c:30:9: warning: incorrect type in argument 4 (different types)
+varargs-type-formattest.c:30:9: expected int
+varargs-type-formattest.c:30:9: got long
+varargs-type-formattest.c:30:9: warning: incorrect type in argument 5 (different types)
+varargs-type-formattest.c:30:9: expected int
+varargs-type-formattest.c:30:9: got long
+varargs-type-formattest.c:31:9: warning: incorrect type for width argument 2
+varargs-type-formattest.c:31:9: expected int
+varargs-type-formattest.c:31:9: got unsigned long
+varargs-type-formattest.c:31:9: warning: incorrect type in argument 4 (different types)
+varargs-type-formattest.c:31:9: expected int
+varargs-type-formattest.c:31:9: got long
+varargs-type-formattest.c:31:9: warning: incorrect type for width argument 3
+varargs-type-formattest.c:31:9: expected int
+varargs-type-formattest.c:31:9: got unsigned long
+varargs-type-formattest.c:31:9: warning: incorrect type in argument 5 (different types)
+varargs-type-formattest.c:31:9: expected int
+varargs-type-formattest.c:31:9: got long
+varargs-type-formattest.c:32:9: warning: incorrect type for position argument 3
+varargs-type-formattest.c:32:9: expected int
+varargs-type-formattest.c:32:9: got unsigned long
+varargs-type-formattest.c:32:9: warning: incorrect type for position argument 4
+varargs-type-formattest.c:32:9: expected int
+varargs-type-formattest.c:32:9: got unsigned long
+ * check-error-end
+ *
+ */
--
2.37.2.352.g3c44437643
^ permalink raw reply related [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-06-19 7:05 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-19 7:05 updated variadic printf/scanf checking Ben Dooks
2026-06-19 7:05 ` [RFC v4 1/4] parse: initial parsing of __attribute__((format)) Ben Dooks
2026-06-19 7:05 ` [RFC v4 2/4] add -Wformat Ben Dooks
2026-06-19 7:05 ` [RFC v4 3/4] evaluate: check variadic argument types against formatting info Ben Dooks
2026-06-19 7:05 ` [RFC v4 4/4] tests: add varargs printf format tests Ben Dooks
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox