<commit_before>#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <assert.h>

#include "../util/where.h"
#include "../util/util.h"
#include "../util/dynarray.h"

#include "expr.h"
#include "decl.h"
#include "const.h"
#include "funcargs.h"
#include "type_is.h"
#include "warn.h"

#include "format_chk.h"

enum printf_attr
{
	printf_attr_long = 1 << 0
};

static void format_check_printf_1(char fmt, type *const t_in,
		where *w, enum printf_attr attr)
{
	int allow_long = 0;
	char expected[BTYPE_STATIC_BUFSIZ];

	expected[0] = '\0';

	switch(fmt){
		enum type_primitive prim;
		type *tt;

		case 's': prim = type_nchar; goto ptr;
		case 'p': prim = type_void; goto ptr;
		case 'n': prim = type_int;  goto ptr;
ptr:
			tt = type_is_primitive(type_is_ptr(t_in), prim);
			if(!tt){
				snprintf(expected, sizeof expected,
						"'%s *'", type_primitive_to_str(prim));
			}
			break;

		case 'x':
		case 'X':
		case 'u':
		case 'o':
			/* unsigned ... */
		case '*':
		case 'c':
		case 'd':
		case 'i':
			allow_long = 1;
			if(!type_is_integral(t_in))
				strcpy(expected, "integral");
			if((attr & printf_attr_long) && !type_is_primitive(t_in, type_long))
				strcpy(expected, "'long'");
			break;

		case 'e':
		case 'E':
		case 'f':
		case 'F':
		case 'g':
		case 'G':
		case 'a':
		case 'A':
			if(!type_is_floating(t_in))
				strcpy(expected, "'double'");
			break;

		default:
			cc1_warn_at(w, attr_printf_unknown,
					"unknown conversion character 0x%x", fmt);
			return;
	}

	if(!*expected && !allow_long && attr & printf_attr_long)
		strcpy(expected, "'long'");

	if(*expected){
		cc1_warn_at(w, attr_printf_bad,
				"format %%%s%c expects %s argument (got %s)",
				attr & printf_attr_long ? "l" : "", fmt,
				expected, type_to_str(t_in));
	}
}

static void format_check_printf_str(
		expr **args,
		const char *fmt, const int len,
		int var_idx, where *w)
{
	int n_arg = 0;
	int i;

	for(i = 0; i < len && fmt[i];){
		if(fmt[i++] == '%'){
			int fin;
			enum printf_attr attr;
			expr *e;

			if(i == len){
				where strloc = *w;
				strloc.chr += i + 1; /* +1 since we start on the '(' */
				cc1_warn_at(&strloc, attr_printf_bad, "incomplete format specifier");
				return;
			}

			if(fmt[i] == '%'){
				i++;
				continue;
			}

recheck:
			attr = 0;
			fin = 0;
			do switch(fmt[i]){
				 case 'l':
					/* TODO: multiple check.. */
					attr |= printf_attr_long;

				case '1': case '2': case '3':
				case '4': case '5': case '6':
				case '7': case '8': case '9':

				case '0': case '#': case '-':
				case ' ': case '+': case '.':

				case 'h': case 'L':
					i++;
					break;
				default:
					fin = 1;
			}while(!fin);

			/* don't check for format(printf, $x, 0) */
			if(var_idx != -1){
				e = args[var_idx + n_arg++];

				if(!e){
					cc1_warn_at(w, attr_printf_bad,
							"too few arguments for format (%%%c)", fmt[i]);
					break;
				}

				format_check_printf_1(fmt[i], e->tree_type, &e->where, attr);
			}

			if(fmt[i] == '*'){
				i++;
				goto recheck;
			}
		}
	}

	if(var_idx != -1 && (!fmt[i] || i == len) && args[var_idx + n_arg])
		cc1_warn_at(w, attr_printf_toomany, "too many arguments for format");
}

static void format_check_printf(
		expr *str_arg,
		expr **args,
		unsigned var_idx,
		where *w)
{
	stringlit *fmt_str;
	consty k;

	const_fold(str_arg, &k);

	switch(k.type){
		case CONST_NO:
		case CONST_NEED_ADDR:<commit_msg>Better printf() format attribute checks<commit_after>#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <assert.h>

#include "../util/where.h"
#include "../util/util.h"
#include "../util/dynarray.h"

#include "expr.h"
#include "decl.h"
#include "const.h"
#include "funcargs.h"
#include "type_is.h"
#include "warn.h"

#include "format_chk.h"

enum printf_attr
{
	printf_attr_long = 1 << 0,
	printf_attr_llong = 1 << 1,
	printf_attr_size_t = 1 << 2
};

static const char *printf_attr_to_str(enum printf_attr attr)
{
	if(attr & printf_attr_size_t)
		return "z";
	if(attr & printf_attr_llong)
		return "ll";
	if(attr & printf_attr_long)
		return "l";
	return NULL;
}

static void warn_printf_attr(char fmt, where *w, enum printf_attr attr)
{
	cc1_warn_at(w, attr_printf_bad,
			"unexpected printf modifier '%s' for %%%c",
			printf_attr_to_str(attr), fmt);
}

static void format_check_printf_1(char fmt, type *const t_in,
		where *w, enum printf_attr attr)
{
	char expected[BTYPE_STATIC_BUFSIZ];

	expected[0] = '\0';

	switch(fmt){
		enum type_primitive prim;
		type *tt;

		case 's': prim = type_nchar; goto ptr;
		case 'p': prim = type_void; goto ptr;
		case 'n': prim = type_int;  goto ptr;
ptr:
			tt = type_is_primitive(type_is_ptr(t_in), prim);
			if(!tt){
				snprintf(expected, sizeof expected,
						"'%s *'", type_primitive_to_str(prim));
			}else if(attr){
				warn_printf_attr(fmt, w, attr);
			}
			break;

		case 'x':
		case 'X':
		case 'u':
		case 'o':
			/* unsigned ... */
		case '*':
		case 'c':
		case 'd':
		case 'i':
			if(!type_is_integral(t_in)){
				strcpy(expected, "integral");
				break;
			}
#define ATTR_CHECK(suff, str)                   \
			if((attr & printf_attr_##suff)            \
			&& !type_is_primitive(t_in, type_##suff)) \
			{                                         \
				strcpy(expected, str);                  \
			}

			ATTR_CHECK(llong, "'long long'")
			else
			ATTR_CHECK(long, "'long'")
			else
			ATTR_CHECK(long, "'size_t'")
			break;

		case 'e':
		case 'E':
		case 'f':
		case 'F':
		case 'g':
		case 'G':
		case 'a':
		case 'A':
			if(!type_is_floating(t_in))
				strcpy(expected, "'double'");
			else if(attr)
				warn_printf_attr(fmt, w, attr);
			break;

		default:
			cc1_warn_at(w, attr_printf_unknown,
					"unknown conversion character 0x%x", fmt);
			return;
	}

	if(*expected){
		cc1_warn_at(w, attr_printf_bad,
				"format %%%s%c expects %s argument (got %s)",
				attr & printf_attr_long ? "l" : "", fmt,
				expected, type_to_str(t_in));
	}
}

static enum printf_attr printf_modifiers(
		const char *fmt, int *index)
{
	enum printf_attr attr = 0;

	for(;;) switch(fmt[*index]){
		case 'l':
			if(attr & printf_attr_long)
				attr |= printf_attr_llong;
			else
				attr |= printf_attr_long;

		case '1': case '2': case '3':
		case '4': case '5': case '6':
		case '7': case '8': case '9':

		case '0': case '#': case '-':
		case ' ': case '+': case '.':

		case 'h': case 'L':
			++*index;
			break;

		case 'z':
			attr |= printf_attr_size_t;
			++*index;
			break;

		default:
			return attr;
	}
}

static void format_check_printf_str(
		expr **args,
		const char *fmt, const int len,
		int var_idx, where *w)
{
	int n_arg = 0;
	int i;

	for(i = 0; i < len && fmt[i];){
		if(fmt[i++] == '%'){
			if(i == len){
				where strloc = *w;
				strloc.chr += i + 1; /* +1 since we start on the '(' */
				cc1_warn_at(&strloc, attr_printf_bad, "incomplete format specifier");
				return;
			}

			if(fmt[i] == '%'){
				i++;
				continue;
			}

			/* don't check for format(printf, ..., 0) */
			if(var_idx != -1){
				expr *e = args[var_idx + n_arg++];
				enum printf_attr attr = printf_modifiers(fmt, &i);

				if(!e){
					cc1_warn_at(w, attr_printf_bad,
							"too few arguments for format (%%%c)", fmt[i]);
					break;
				}

				format_check_printf_1(fmt[i], e->tree_type, &e->where, attr);
			}

			/*if(fmt[i] == '*'){
				i++;
				goto recheck;
			}*/
		}
	}

	if(var_idx != -1 && (!fmt[i] || i == len) && args[var_idx + n_arg])
		cc1_warn_at(w, attr_printf_toomany, "too many arguments for format");
}

static void format_check_printf(
		expr *str_arg,
		expr **args,
		unsigned var_idx,
		where *w)
{
	stringlit *fmt_str;
	consty k;

	const_fold(str_arg, &k);

	switch(k.type){
		case CONST_NO:
		case CONST_NEED_ADDR:
{'fraction_non_alphanumeric': 0.17590797466718366, 'fraction_numerical': 0.008918185343156262, 'mean_word_length': 3.068349106203996, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6018692', 'n_tokens_mistral': 3645, 'n_tokens_neox': 3101, 'n_words': 751}