1 /* $NetBSD: cksnprintb.c,v 1.16 2025/08/31 20:43:27 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.16 2025/08/31 20:43:27 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 parse_description(checker *ck, const quoted_iterator *dir) 139 { 140 size_t descr_start = 0; 141 quoted_iterator it = ck->it; 142 uint64_t end_marker = ck->new_style ? 0 : 32; 143 144 while (quoted_next(ck->fmt, &it) && it.value > end_marker) { 145 ck->it = it; 146 if (descr_start == 0) 147 descr_start = it.start; 148 if (it.escaped) 149 /* escaped character '%.*s' in description ... */ 150 warning(363, 151 len(it), start(it, ck->fmt), 152 range(*dir, it), start(*dir, ck->fmt)); 153 } 154 return descr_start > 0; 155 } 156 157 static bool 158 check_conversion(checker *ck) 159 { 160 bool new_style = ck->new_style; 161 const buffer *fmt = ck->fmt; 162 quoted_iterator *it = &ck->it; 163 164 if (!quoted_next(fmt, it)) 165 return false; 166 quoted_iterator dir = *it; 167 168 bool has_bit = !new_style 169 || dir.value == 'b' || dir.value == 'f' || dir.value == 'F'; 170 if (has_bit && new_style && !quoted_next(fmt, it)) { 171 /* missing bit position after '%.*s' */ 172 warning(364, range(dir, *it), start(dir, fmt)); 173 return false; 174 } 175 /* LINTED 86 "automatic 'bit' hides external declaration" */ 176 quoted_iterator bit = *it; 177 178 bool has_width = new_style 179 && (dir.value == 'f' || dir.value == 'F'); 180 if (has_width && !quoted_next(fmt, it)) { 181 /* missing field width after '%.*s' */ 182 warning(365, range(dir, *it), start(dir, fmt)); 183 return false; 184 } 185 quoted_iterator width = *it; 186 187 bool has_cmp = new_style 188 && (dir.value == '=' || dir.value == ':'); 189 if (has_cmp && !quoted_next(fmt, it)) { 190 /* missing comparison value after conversion '%.*s' */ 191 warning(368, range(dir, *it), start(dir, fmt)); 192 return false; 193 } 194 quoted_iterator cmp = *it; 195 196 bool has_default = new_style && dir.value == '*'; 197 198 if (dir.value == '\0') { 199 quoted_iterator end = *it; 200 if (!quoted_next(fmt, &end)) { 201 /* redundant '\0' at the end of the format */ 202 warning(377); 203 return false; 204 } 205 } 206 207 if (!has_bit && !has_cmp && !has_default) { 208 /* unknown conversion '%.*s', must be one of 'bfF=:*' */ 209 warning(374, len(dir), start(dir, fmt)); 210 return false; 211 } 212 if (new_style && dir.escaped) 213 /* conversion '%.*s' should not be escaped */ 214 warning(362, len(dir), start(dir, fmt)); 215 216 bool needs_descr = !(new_style && dir.value == 'F'); 217 bool seen_descr = parse_description(ck, &dir); 218 bool seen_null = new_style 219 && quoted_next(ck->fmt, &ck->it) && ck->it.value == 0; 220 221 if (has_bit) 222 ck->field_kind = (char)dir.value; 223 if (ck->field_kind != '\0' 224 && ((dir.value == '=' && ck->field_kind != 'f') 225 || (dir.value == ':' && ck->field_kind != 'F'))) 226 /* conversion '%.*s' does not mix with '%c' */ 227 warning(386, len(dir), start(dir, fmt), ck->field_kind); 228 229 if (has_bit) 230 check_hex_escape(fmt, bit); 231 if (has_width) 232 check_hex_escape(fmt, width); 233 if (has_bit && bit.octal_digits == 0 && bit.hex_digits == 0) 234 /* bit position '%.*s' in '%.*s' should be escaped as ... */ 235 warning(369, len(bit), start(bit, fmt), 236 range(dir, *it), start(dir, fmt)); 237 if (has_width && width.octal_digits == 0 && width.hex_digits == 0) 238 /* field width '%.*s' in '%.*s' should be escaped as ... */ 239 warning(370, len(width), start(width, fmt), 240 range(dir, *it), start(dir, fmt)); 241 if (has_bit && (new_style ? bit.value > 63 : bit.value - 1 > 31)) 242 /* bit position '%.*s' (%ju) in '%.*s' out of range %u..%u */ 243 warning(371, 244 len(bit), start(bit, fmt), val(bit), 245 range(dir, *it), start(dir, fmt), 246 new_style ? 0 : 1, new_style ? 63 : 32); 247 if (has_width && width.value > 64) 248 /* field width '%.*s' (%ju) in '%.*s' out of range 0..64 */ 249 warning(372, 250 len(width), start(width, fmt), val(width), 251 range(dir, *it), start(dir, fmt)); 252 if (has_width && bit.value + width.value > 64) 253 /* bit field end %ju in '%.*s' out of range 0..64 */ 254 warning(373, val(bit) + val(width), 255 range(dir, *it), start(dir, fmt)); 256 if (has_cmp && ck->field_width > 0 && ck->field_width < 64 257 && cmp.value & ~value_bits((unsigned)ck->field_width)) 258 /* comparison value '%.*s' (%ju) exceeds maximum field ... */ 259 warning(375, len(cmp), start(cmp, fmt), val(cmp), 260 (uintmax_t)value_bits((unsigned)ck->field_width)); 261 if (has_bit) 262 check_bit(ck, bit.value, has_width ? width.value : 1, 263 ck->fmt->data + dir.start, (int)(it->end - dir.start)); 264 if (needs_descr && !seen_descr) 265 /* empty description in '%.*s' */ 266 warning(367, range(dir, *it), start(dir, fmt)); 267 if (new_style && !seen_null) 268 /* missing '\0' at the end of '%.*s' */ 269 warning(366, range(dir, *it), start(dir, fmt)); 270 271 if (has_width) 272 ck->field_width = width.value; 273 return true; 274 } 275 276 void 277 check_snprintb(const function_call *call) 278 { 279 const char *name; 280 const buffer *fmt; 281 const tnode_t *value; 282 283 if (!(call->func->tn_op == ADDR 284 && call->func->u.ops.left->tn_op == NAME 285 && (name = call->func->u.ops.left->u.sym->s_name, true) 286 && ((strcmp(name, "snprintb") == 0 && call->args_len == 4) 287 || (strcmp(name, "snprintb_m") == 0 && call->args_len == 5)) 288 && call->args[2]->tn_op == CVT 289 && call->args[2]->u.ops.left->tn_op == ADDR 290 && call->args[2]->u.ops.left->u.ops.left->tn_op == STRING 291 && (fmt = call->args[2]->u.ops.left->u.ops.left->u.str_literals, 292 fmt->data != NULL) 293 && (value = call->args[3], true))) 294 return; 295 296 checker ck = { 297 .fmt = fmt, 298 .possible_value_bits = possible_bits(value), 299 .field_width = 64, 300 }; 301 302 if (!quoted_next(fmt, &ck.it)) { 303 /* missing new-style '\177' or old-style number base */ 304 warning(359); 305 return; 306 } 307 ck.new_style = ck.it.value == '\177'; 308 if (ck.new_style && !quoted_next(fmt, &ck.it)) { 309 /* missing new-style number base after '\177' */ 310 warning(360); 311 return; 312 } 313 if (ck.it.value != 8 && ck.it.value != 10 && ck.it.value != 16) { 314 /* number base '%.*s' is %ju, must be 8, 10 or 16 */ 315 warning(361, len(ck.it), start(ck.it, fmt), val(ck.it)); 316 return; 317 } 318 319 while (check_conversion(&ck)) 320 continue; 321 } 322