Home | History | Annotate | Line # | Download | only in lint1
cksnprintb.c revision 1.2
      1 /*	$NetBSD: cksnprintb.c,v 1.2 2024/03/01 21:52:48 rillig Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 2024 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * This code is derived from software contributed to The NetBSD Foundation
      8  * by Roland Illig <rillig (at) NetBSD.org>.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  * 1. Redistributions of source code must retain the above copyright
     14  *    notice, this list of conditions and the following disclaimer.
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     29  * POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 #if HAVE_NBTOOL_CONFIG_H
     33 #include "nbtool_config.h"
     34 #endif
     35 
     36 #include <sys/cdefs.h>
     37 #if defined(__RCSID)
     38 __RCSID("$NetBSD: cksnprintb.c,v 1.2 2024/03/01 21:52:48 rillig Exp $");
     39 #endif
     40 
     41 #include <stdbool.h>
     42 #include <string.h>
     43 
     44 #include "lint1.h"
     45 
     46 static bool
     47 match_string_literal(const tnode_t *tn, const buffer **str)
     48 {
     49 	while (tn->tn_op == CVT)
     50 		tn = tn_ck_left(tn);
     51 	return tn->tn_op == ADDR
     52 	    && tn->tn_left->tn_op == STRING
     53 	    && (*str = tn->tn_left->tn_string, (*str)->data != NULL);
     54 }
     55 
     56 static bool
     57 match_snprintb_call(const function_call *call,
     58     const buffer **out_fmt, const tnode_t **out_val)
     59 {
     60 	const char *func;
     61 	const tnode_t *val;
     62 	const buffer *str;
     63 
     64 	if (call->func->tn_op == ADDR
     65 	    && call->func->tn_left->tn_op == NAME
     66 	    && (func = call->func->tn_left->tn_sym->s_name, true)
     67 	    && ((strcmp(func, "snprintb") == 0 && call->args_len == 4)
     68 		|| (strcmp(func, "snprintb_m") == 0 && call->args_len == 5))
     69 	    && match_string_literal(call->args[2], &str)
     70 	    && (val = call->args[3], true)) {
     71 		*out_fmt = str;
     72 		*out_val = val;
     73 		return true;
     74 	}
     75 	return false;
     76 }
     77 
     78 static int
     79 len(quoted_iterator it)
     80 {
     81 	return (int)(it.i - it.start);
     82 }
     83 
     84 static int
     85 range(quoted_iterator start, quoted_iterator end)
     86 {
     87 	return (int)(end.i - start.start);
     88 }
     89 
     90 static const char *
     91 start(quoted_iterator it, const buffer *buf)
     92 {
     93 	return buf->data + it.start;
     94 }
     95 
     96 static uintmax_t
     97 val(quoted_iterator it)
     98 {
     99 	return it.value;
    100 }
    101 
    102 static void
    103 check_hex_escape(const buffer *buf, quoted_iterator it)
    104 {
    105 	if (it.hex_digits > 1) {
    106 		bool upper = false;
    107 		bool lower = false;
    108 		for (size_t i = it.start + 2; i < it.i; i++) {
    109 			if (isupper((unsigned char)buf->data[i]))
    110 				upper = true;
    111 			if (islower((unsigned char)buf->data[i]))
    112 				lower = true;
    113 		}
    114 		if (upper && lower)
    115 			/* hex escape '%.*s' mixes uppercase and lower... */
    116 			warning(357, len(it), start(it, buf));
    117 	}
    118 	if (it.hex_digits > 2)
    119 		/* hex escape '%.*s' has more than 2 digits */
    120 		warning(358, len(it), start(it, buf));
    121 }
    122 
    123 static bool
    124 check_directive(const buffer *fmt, quoted_iterator *it, bool new_style,
    125 		uint64_t *prev_field_width)
    126 {
    127 
    128 	if (!quoted_next(fmt, it))
    129 		return false;
    130 	quoted_iterator dir = *it;
    131 
    132 	bool has_bit = !new_style
    133 	    || dir.value == 'b' || dir.value == 'f' || dir.value == 'F';
    134 	if (has_bit && new_style && !quoted_next(fmt, it)) {
    135 		/* missing bit position after '%.*s' */
    136 		warning(364, len(dir), start(dir, fmt));
    137 		return false;
    138 	}
    139 	/* LINTED 86 "automatic 'bit' hides external declaration" */
    140 	quoted_iterator bit = *it;
    141 
    142 	bool has_width = new_style
    143 	    && (dir.value == 'f' || dir.value == 'F');
    144 	if (has_width && !quoted_next(fmt, it)) {
    145 		/* missing field width after '%.*s' */
    146 		warning(365, range(dir, bit), start(dir, fmt));
    147 		return false;
    148 	}
    149 	quoted_iterator width = *it;
    150 
    151 	bool has_cmp = new_style
    152 	    && (dir.value == '=' || dir.value == ':');
    153 	if (has_cmp && !quoted_next(fmt, it)) {
    154 		/* missing comparison value after directive '%.*s' */
    155 		warning(368, range(dir, bit), start(dir, fmt));
    156 		return false;
    157 	}
    158 	quoted_iterator cmp = *it;
    159 
    160 	bool has_default = new_style && dir.value == '*';
    161 	if (has_default && !quoted_next(fmt, it)) {
    162 		/* missing '\0' at the end of '%.*s' */
    163 		warning(366, range(dir, *it), start(dir, fmt));
    164 		return false;
    165 	}
    166 
    167 	if (!has_bit && !has_cmp && !has_default) {
    168 		/* unknown directive '%.*s' */
    169 		warning(374, len(dir), start(dir, fmt));
    170 		return false;
    171 	}
    172 
    173 	if (!quoted_next(fmt, it)) {
    174 		if (new_style && dir.value != '*')
    175 			/* missing '\0' at the end of '%.*s' */
    176 			warning(366, range(dir, *it), start(dir, fmt));
    177 		else
    178 			/* empty description in '%.*s' */
    179 			warning(367, range(dir, *it), start(dir, fmt));
    180 		return false;
    181 	}
    182 	quoted_iterator descr = *it;
    183 
    184 	quoted_iterator prev = *it;
    185 	for (;;) {
    186 		if (new_style && it->value == 0)
    187 			break;
    188 		if (!new_style && it->value == 0)
    189 			/* old-style format contains '\0' */
    190 			warning(362);
    191 		if (!new_style && it->value <= 32) {
    192 			*it = prev;
    193 			break;
    194 		}
    195 		if (it->escaped && !isprint((unsigned char)it->value)) {
    196 			/* non-printing character '%.*s' in description ... */
    197 			warning(363,
    198 			    len(*it), start(*it, fmt),
    199 			    range(descr, *it), start(descr, fmt));
    200 		}
    201 		prev = *it;
    202 		if (!quoted_next(fmt, it)) {
    203 			if (new_style) {
    204 				/* missing '\0' at the end of '%.*s' */
    205 				warning(366, range(dir, prev),
    206 				    start(dir, fmt));
    207 			}
    208 			break;
    209 		}
    210 	}
    211 
    212 	if (has_bit)
    213 		check_hex_escape(fmt, bit);
    214 	if (has_width)
    215 		check_hex_escape(fmt, width);
    216 	if (has_bit && bit.octal_digits == 0 && bit.hex_digits == 0) {
    217 		/* bit position '%.*s' in '%.*s' should be escaped as ... */
    218 		warning(369, len(bit), start(bit, fmt),
    219 		    range(dir, *it), start(dir, fmt));
    220 	}
    221 	if (has_width && width.octal_digits == 0 && width.hex_digits == 0) {
    222 		/* field width '%.*s' in '%.*s' should be escaped as ... */
    223 		warning(370, len(width), start(width, fmt),
    224 		    range(dir, *it), start(dir, fmt));
    225 	}
    226 	if (has_bit && (new_style ? bit.value > 63 : bit.value - 1 > 31)) {
    227 		/* bit position '%.*s' (%ju) in '%.*s' out of range %u..%u */
    228 		warning(371,
    229 		    len(bit), start(bit, fmt), val(bit),
    230 		    range(dir, *it), start(dir, fmt),
    231 		    new_style ? 0 : 1, new_style ? 63 : 32);
    232 	}
    233 	if (has_width && width.value > (new_style ? 64 : 32)) {
    234 		/* field width '%.*s' (%ju) in '%.*s' out of range 0..%u */
    235 		warning(372,
    236 		    len(width), start(width, fmt), val(width),
    237 		    range(dir, *it), start(dir, fmt),
    238 		    new_style ? 64 : 32);
    239 	}
    240 	if (has_width && bit.value + width.value > 64) {
    241 		/* bit field end %ju in '%.*s' out of range 0..64 */
    242 		warning(373, val(bit) + val(width),
    243 		    range(dir, *it), start(dir, fmt));
    244 	}
    245 	if (has_cmp && *prev_field_width < 64
    246 	    && cmp.value & ~(uint64_t)0 << *prev_field_width) {
    247 		/* comparison value '%.*s' (%ju) exceeds field width %ju */
    248 		warning(375, len(cmp), start(cmp, fmt), val(cmp),
    249 		    *prev_field_width);
    250 	}
    251 	if (descr.i == prev.i && dir.value != 'F') {
    252 		/* empty description in '%.*s' */
    253 		warning(367, range(dir, *it), start(dir, fmt));
    254 	}
    255 
    256 	if (has_width)
    257 		*prev_field_width = width.value;
    258 	return true;
    259 }
    260 
    261 void
    262 check_snprintb(const tnode_t *expr)
    263 {
    264 	const buffer *fmt;
    265 	const tnode_t *value;
    266 	if (!match_snprintb_call(expr->tn_call, &fmt, &value))
    267 		return;
    268 
    269 	quoted_iterator it = { .i = 0 };
    270 	if (!quoted_next(fmt, &it)) {
    271 		/* missing new-style '\177' or old-style number base */
    272 		warning(359);
    273 		return;
    274 	}
    275 	bool new_style = it.value == '\177';
    276 	if (new_style && !quoted_next(fmt, &it)) {
    277 		/* missing new-style number base after '\177' */
    278 		warning(360);
    279 		return;
    280 	}
    281 	if (it.value != 8 && it.value != 10 && it.value != 16) {
    282 		/* number base '%.*s' is %ju, should be 8, 10 or 16 */
    283 		warning(361, len(it), start(it, fmt), val(it));
    284 		return;
    285 	}
    286 
    287 	uint64_t prev_field_width = 64;
    288 	while (check_directive(fmt, &it, new_style, &prev_field_width))
    289 		continue;
    290 }
    291