1 /* $NetBSD: cksnprintb.c,v 1.18 2026/04/20 22:17:08 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.18 2026/04/20 22:17:08 rillig Exp $"); 39 #endif 40 41 #include <stdbool.h> 42 #include <string.h> 43 44 #include "lint1.h" 45 46 typedef struct { 47 bool new_style; 48 const buffer *fmt; 49 uint64_t possible_value_bits; 50 51 quoted_iterator it; 52 uint64_t field_width; 53 uint64_t covered; 54 const char *covered_start[64]; 55 int covered_len[64]; 56 char field_kind; /* 'f' or 'F' or '\0' */ 57 } checker; 58 59 static int 60 len(quoted_iterator it) 61 { 62 return (int)(it.end - it.start); 63 } 64 65 static int 66 range(quoted_iterator start, quoted_iterator end) 67 { 68 return (int)(end.end - start.start); 69 } 70 71 static const char * 72 start(quoted_iterator it, const buffer *buf) 73 { 74 return buf->data + it.start; 75 } 76 77 static uintmax_t 78 val(quoted_iterator it) 79 { 80 return it.value; 81 } 82 83 static void 84 check_hex_escape(const buffer *buf, quoted_iterator it) 85 { 86 if (it.hex_digits > 1) { 87 bool upper = false; 88 bool lower = false; 89 for (size_t i = it.start + 2; i < it.end; i++) { 90 if (ch_isupper(buf->data[i])) 91 upper = true; 92 if (ch_islower(buf->data[i])) 93 lower = true; 94 } 95 if (upper && lower) 96 /* hex escape '%.*s' mixes uppercase and lower... */ 97 warning(357, len(it), start(it, buf)); 98 } 99 if (it.hex_digits > 2) 100 /* hex escape '%.*s' has more than 2 digits */ 101 warning(358, len(it), start(it, buf)); 102 } 103 104 static void 105 check_bit(checker *ck, uint64_t dir_lsb, uint64_t width, 106 const char *start, int len) 107 { 108 unsigned lsb = (unsigned)(ck->new_style ? dir_lsb : dir_lsb - 1); 109 if (lsb >= 64 || width == 0 || width > 64) 110 return; 111 112 uint64_t field_mask = value_bits((unsigned)width) << lsb; 113 for (unsigned i = lsb; i < 64; i++) { 114 if (ck->covered & field_mask & bit(i)) { 115 /* '%.*s' overlaps earlier '%.*s' on bit %u */ 116 warning(376, 117 len, start, 118 ck->covered_len[i], ck->covered_start[i], 119 ck->new_style ? i : i + 1); 120 break; 121 } 122 } 123 124 ck->covered |= field_mask; 125 for (unsigned i = lsb; i < 64; i++) { 126 if (field_mask & bit(i)) { 127 ck->covered_start[i] = start; 128 ck->covered_len[i] = len; 129 } 130 } 131 132 if (!(ck->possible_value_bits & field_mask)) 133 /* conversion '%.*s' is unreachable by input value */ 134 warning(378, len, start); 135 } 136 137 static bool 138 descr_starts_with_identifier(const checker *ck) 139 { 140 quoted_iterator it = ck->it; 141 142 return quoted_next(ck->fmt, &it) 143 && it.value <= 0x7f 144 && (ch_isalnum((char)it.value) || it.value == '_'); 145 } 146 147 static bool 148 parse_description(checker *ck, const quoted_iterator *dir) 149 { 150 size_t descr_start = 0; 151 quoted_iterator it = ck->it; 152 uint64_t end_marker = ck->new_style ? 0 : 32; 153 154 while (quoted_next(ck->fmt, &it) && it.value > end_marker) { 155 ck->it = it; 156 if (descr_start == 0) 157 descr_start = it.start; 158 if (it.escaped) 159 /* escaped character '%.*s' in description ... */ 160 warning(363, 161 len(it), start(it, ck->fmt), 162 range(*dir, it), start(*dir, ck->fmt)); 163 } 164 return descr_start > 0; 165 } 166 167 static bool 168 check_conversion(checker *ck) 169 { 170 bool new_style = ck->new_style; 171 const buffer *fmt = ck->fmt; 172 quoted_iterator *it = &ck->it; 173 174 if (!quoted_next(fmt, it)) 175 return false; 176 quoted_iterator dir = *it; 177 178 bool has_bit = !new_style 179 || dir.value == 'b' || dir.value == 'f' || dir.value == 'F'; 180 if (has_bit && new_style && !quoted_next(fmt, it)) { 181 /* missing bit position after '%.*s' */ 182 warning(364, range(dir, *it), start(dir, fmt)); 183 return false; 184 } 185 /* LINTED 86 "automatic 'bit' hides external declaration" */ 186 quoted_iterator bit = *it; 187 188 bool has_width = new_style 189 && (dir.value == 'f' || dir.value == 'F'); 190 if (has_width && !quoted_next(fmt, it)) { 191 /* missing field width after '%.*s' */ 192 warning(365, range(dir, *it), start(dir, fmt)); 193 return false; 194 } 195 quoted_iterator width = *it; 196 197 bool has_cmp = new_style 198 && (dir.value == '=' || dir.value == ':'); 199 if (has_cmp && !quoted_next(fmt, it)) { 200 /* missing comparison value after conversion '%.*s' */ 201 warning(368, range(dir, *it), start(dir, fmt)); 202 return false; 203 } 204 quoted_iterator cmp = *it; 205 206 bool has_default = new_style && dir.value == '*'; 207 208 if (dir.value == '\0') { 209 quoted_iterator end = *it; 210 if (!quoted_next(fmt, &end)) { 211 /* redundant '\0' at the end of the format */ 212 warning(377); 213 return false; 214 } 215 } 216 217 if (!has_bit && !has_cmp && !has_default) { 218 /* unknown conversion '%.*s', must be one of 'bfF=:*' */ 219 warning(374, len(dir), start(dir, fmt)); 220 return false; 221 } 222 if (new_style && dir.escaped) 223 /* conversion '%.*s' should not be escaped */ 224 warning(362, len(dir), start(dir, fmt)); 225 226 bool needs_descr = !(new_style && dir.value == 'F'); 227 bool descr_ident = descr_starts_with_identifier(ck); 228 bool seen_descr = parse_description(ck, &dir); 229 bool seen_null = new_style 230 && quoted_next(ck->fmt, &ck->it) && ck->it.value == 0; 231 232 if (has_bit) 233 ck->field_kind = (char)dir.value; 234 if (ck->field_kind != '\0' 235 && ((dir.value == '=' && ck->field_kind != 'f') 236 || (dir.value == ':' && ck->field_kind != 'F' 237 && !(ck->field_kind == 'f' && !descr_ident)))) 238 /* conversion '%.*s' from '%.*s' does not mix with '%c' */ 239 warning(386, len(dir), start(dir, fmt), 240 range(dir, *it), start(dir, fmt), ck->field_kind); 241 242 if (has_bit) 243 check_hex_escape(fmt, bit); 244 if (has_width) 245 check_hex_escape(fmt, width); 246 if (has_bit && bit.octal_digits == 0 && bit.hex_digits == 0) 247 /* bit position '%.*s' in '%.*s' should be escaped as ... */ 248 warning(369, len(bit), start(bit, fmt), 249 range(dir, *it), start(dir, fmt)); 250 if (has_width && width.octal_digits == 0 && width.hex_digits == 0) 251 /* field width '%.*s' in '%.*s' should be escaped as ... */ 252 warning(370, len(width), start(width, fmt), 253 range(dir, *it), start(dir, fmt)); 254 if (has_bit && (new_style ? bit.value > 63 : bit.value - 1 > 31)) 255 /* bit position '%.*s' (%ju) in '%.*s' out of range %u..%u */ 256 warning(371, 257 len(bit), start(bit, fmt), val(bit), 258 range(dir, *it), start(dir, fmt), 259 new_style ? 0 : 1, new_style ? 63 : 32); 260 if (has_width && width.value > 64) 261 /* field width '%.*s' (%ju) in '%.*s' out of range 0..64 */ 262 warning(372, 263 len(width), start(width, fmt), val(width), 264 range(dir, *it), start(dir, fmt)); 265 if (has_width && bit.value + width.value > 64) 266 /* bit field end %ju in '%.*s' out of range 0..64 */ 267 warning(373, val(bit) + val(width), 268 range(dir, *it), start(dir, fmt)); 269 if (has_cmp && ck->field_width > 0 && ck->field_width < 64 270 && cmp.value & ~value_bits((unsigned)ck->field_width)) 271 /* comparison value '%.*s' (%ju) exceeds maximum field ... */ 272 warning(375, len(cmp), start(cmp, fmt), val(cmp), 273 (uintmax_t)value_bits((unsigned)ck->field_width)); 274 if (has_bit) 275 check_bit(ck, bit.value, has_width ? width.value : 1, 276 ck->fmt->data + dir.start, (int)(it->end - dir.start)); 277 if (needs_descr && !seen_descr) 278 /* empty description in '%.*s' */ 279 warning(367, range(dir, *it), start(dir, fmt)); 280 if (new_style && !seen_null) 281 /* missing '\0' at the end of '%.*s' */ 282 warning(366, range(dir, *it), start(dir, fmt)); 283 284 if (has_width) 285 ck->field_width = width.value; 286 return true; 287 } 288 289 void 290 check_snprintb(const function_call *call) 291 { 292 const char *name; 293 const buffer *fmt; 294 const tnode_t *value; 295 296 if (!(call->func->tn_op == ADDR 297 && call->func->u.ops.left->tn_op == NAME 298 && (name = call->func->u.ops.left->u.sym->s_name, true) 299 && ((strcmp(name, "snprintb") == 0 && call->args_len == 4) 300 || (strcmp(name, "snprintb_m") == 0 && call->args_len == 5)) 301 && call->args[2]->tn_op == CVT 302 && call->args[2]->u.ops.left->tn_op == ADDR 303 && call->args[2]->u.ops.left->u.ops.left->tn_op == STRING 304 && (fmt = call->args[2]->u.ops.left->u.ops.left->u.str_literals, 305 fmt->data != NULL) 306 && (value = call->args[3], true))) 307 return; 308 309 checker ck = { 310 .fmt = fmt, 311 .possible_value_bits = possible_bits(value), 312 .field_width = 64, 313 }; 314 315 if (!quoted_next(fmt, &ck.it)) { 316 /* missing new-style '\177' or old-style number base */ 317 warning(359); 318 return; 319 } 320 ck.new_style = ck.it.value == '\177'; 321 if (ck.new_style && !quoted_next(fmt, &ck.it)) { 322 /* missing new-style number base after '\177' */ 323 warning(360); 324 return; 325 } 326 if (ck.it.value != 8 && ck.it.value != 10 && ck.it.value != 16) { 327 /* number base '%.*s' is %ju, must be 8, 10 or 16 */ 328 warning(361, len(ck.it), start(ck.it, fmt), val(ck.it)); 329 return; 330 } 331 332 while (check_conversion(&ck)) 333 continue; 334 } 335