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