Module Name:    src
Committed By:   rillig
Date:           Sat Mar  2 11:56:37 UTC 2024

Modified Files:
        src/distrib/sets/lists/tests: mi
        src/tests/usr.bin/xlint/lint1: msg_369.c msg_370.c msg_372.c msg_373.c
            msg_375.c t_usage.sh
        src/usr.bin/xlint/lint1: cksnprintb.c err.c
Added Files:
        src/tests/usr.bin/xlint/lint1: msg_376.c

Log Message:
lint: check snprintb formats for overlapping bits and fields


To generate a diff of this commit:
cvs rdiff -u -r1.1307 -r1.1308 src/distrib/sets/lists/tests/mi
cvs rdiff -u -r1.1 -r1.2 src/tests/usr.bin/xlint/lint1/msg_369.c \
    src/tests/usr.bin/xlint/lint1/msg_370.c \
    src/tests/usr.bin/xlint/lint1/msg_372.c \
    src/tests/usr.bin/xlint/lint1/msg_373.c \
    src/tests/usr.bin/xlint/lint1/msg_375.c
cvs rdiff -u -r0 -r1.1 src/tests/usr.bin/xlint/lint1/msg_376.c
cvs rdiff -u -r1.16 -r1.17 src/tests/usr.bin/xlint/lint1/t_usage.sh
cvs rdiff -u -r1.2 -r1.3 src/usr.bin/xlint/lint1/cksnprintb.c
cvs rdiff -u -r1.227 -r1.228 src/usr.bin/xlint/lint1/err.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/distrib/sets/lists/tests/mi
diff -u src/distrib/sets/lists/tests/mi:1.1307 src/distrib/sets/lists/tests/mi:1.1308
--- src/distrib/sets/lists/tests/mi:1.1307	Fri Mar  1 19:39:28 2024
+++ src/distrib/sets/lists/tests/mi	Sat Mar  2 11:56:37 2024
@@ -1,4 +1,4 @@
-# $NetBSD: mi,v 1.1307 2024/03/01 19:39:28 rillig Exp $
+# $NetBSD: mi,v 1.1308 2024/03/02 11:56:37 rillig Exp $
 #
 # Note: don't delete entries from here - mark them as "obsolete" instead.
 #
@@ -7490,6 +7490,7 @@
 ./usr/tests/usr.bin/xlint/lint1/msg_373.c			tests-usr.bin-tests	compattestfile,atf
 ./usr/tests/usr.bin/xlint/lint1/msg_374.c			tests-usr.bin-tests	compattestfile,atf
 ./usr/tests/usr.bin/xlint/lint1/msg_375.c			tests-usr.bin-tests	compattestfile,atf
+./usr/tests/usr.bin/xlint/lint1/msg_376.c			tests-usr.bin-tests	compattestfile,atf
 ./usr/tests/usr.bin/xlint/lint1/op_colon.c			tests-usr.bin-tests	compattestfile,atf
 ./usr/tests/usr.bin/xlint/lint1/op_colon.exp			tests-obsolete		obsolete,atf
 ./usr/tests/usr.bin/xlint/lint1/op_shl_lp64.c			tests-usr.bin-tests	compattestfile,atf

Index: src/tests/usr.bin/xlint/lint1/msg_369.c
diff -u src/tests/usr.bin/xlint/lint1/msg_369.c:1.1 src/tests/usr.bin/xlint/lint1/msg_369.c:1.2
--- src/tests/usr.bin/xlint/lint1/msg_369.c:1.1	Fri Mar  1 19:39:29 2024
+++ src/tests/usr.bin/xlint/lint1/msg_369.c	Sat Mar  2 11:56:37 2024
@@ -1,4 +1,4 @@
-/*	$NetBSD: msg_369.c,v 1.1 2024/03/01 19:39:29 rillig Exp $	*/
+/*	$NetBSD: msg_369.c,v 1.2 2024/03/02 11:56:37 rillig Exp $	*/
 # 3 "msg_369.c"
 
 // Test for message: bit position '%.*s' in '%.*s' should be escaped as octal or hex [369]
@@ -42,11 +42,11 @@ example(unsigned u32, uint64_t u64)
 	    "b\nnewline\0",
 	    u64);
 
-	/* expect+6: warning: bit position '\t' in 'f\t\010tab\0' should be escaped as octal or hex [369] */
-	/* expect+5: warning: bit position '\n' in 'F\n\010newline\0' should be escaped as octal or hex [369] */
+	/* expect+6: warning: bit position '\t' in 'f\t\001tab\0' should be escaped as octal or hex [369] */
+	/* expect+5: warning: bit position '\n' in 'F\n\001newline\0' should be escaped as octal or hex [369] */
 	snprintb(buf, sizeof(buf),
 	    "\177\020"
-	    "f\t\010tab\0"
-	    "F\n\010newline\0",
+	    "f\t\001tab\0"
+	    "F\n\001newline\0",
 	    u64);
 }
Index: src/tests/usr.bin/xlint/lint1/msg_370.c
diff -u src/tests/usr.bin/xlint/lint1/msg_370.c:1.1 src/tests/usr.bin/xlint/lint1/msg_370.c:1.2
--- src/tests/usr.bin/xlint/lint1/msg_370.c:1.1	Fri Mar  1 19:39:29 2024
+++ src/tests/usr.bin/xlint/lint1/msg_370.c	Sat Mar  2 11:56:37 2024
@@ -1,4 +1,4 @@
-/*	$NetBSD: msg_370.c,v 1.1 2024/03/01 19:39:29 rillig Exp $	*/
+/*	$NetBSD: msg_370.c,v 1.2 2024/03/02 11:56:37 rillig Exp $	*/
 # 3 "msg_370.c"
 
 // Test for message: field width '%.*s' in '%.*s' should be escaped as octal or hex [370]
@@ -34,6 +34,7 @@ example(uint64_t u64)
 	    "f\t\ttab\0"
 	    "f\n\nnewline\0",
 	    u64);
+	/* expect-1: warning: 'f\n\nnewline\0' overlaps earlier 'f\t\ttab\0' on bit 10 [376] */
 
 	/* expect+11: warning: bit position ' ' in 'F  space\0' should be escaped as octal or hex [369] */
 	/* expect+10: warning: field width ' ' in 'F  space\0' should be escaped as octal or hex [370] */
@@ -47,4 +48,5 @@ example(uint64_t u64)
 	    "F\t\ttab\0"
 	    "F\n\nnewline\0",
 	    u64);
+	/* expect-1: warning: 'F\n\nnewline\0' overlaps earlier 'F\t\ttab\0' on bit 10 [376] */
 }
Index: src/tests/usr.bin/xlint/lint1/msg_372.c
diff -u src/tests/usr.bin/xlint/lint1/msg_372.c:1.1 src/tests/usr.bin/xlint/lint1/msg_372.c:1.2
--- src/tests/usr.bin/xlint/lint1/msg_372.c:1.1	Fri Mar  1 19:39:29 2024
+++ src/tests/usr.bin/xlint/lint1/msg_372.c	Sat Mar  2 11:56:37 2024
@@ -1,4 +1,4 @@
-/*	$NetBSD: msg_372.c,v 1.1 2024/03/01 19:39:29 rillig Exp $	*/
+/*	$NetBSD: msg_372.c,v 1.2 2024/03/02 11:56:37 rillig Exp $	*/
 # 3 "msg_372.c"
 
 // Test for message: field width '%.*s' (%ju) in '%.*s' out of range 0..%u [372]
@@ -20,9 +20,10 @@ example(uint64_t u64)
 {
 	char buf[64];
 
-	/* expect+11: warning: field width '\101' (65) in 'f\000\101all+1\0' out of range 0..64 [372] */
-	/* expect+10: warning: bit field end 65 in 'f\000\101all+1\0' out of range 0..64 [373] */
-	/* expect+9: warning: bit field end 65 in 'f\001\100oob64\0' out of range 0..64 [373] */
+	/* expect+12: warning: field width '\101' (65) in 'f\000\101all+1\0' out of range 0..64 [372] */
+	/* expect+11: warning: bit field end 65 in 'f\000\101all+1\0' out of range 0..64 [373] */
+	/* expect+10: warning: bit field end 65 in 'f\001\100oob64\0' out of range 0..64 [373] */
+	/* expect+9: warning: 'f\001\100oob64\0' overlaps earlier 'f\000\100all\0' on bit 1 [376] */
 	/* expect+8: warning: field width '\377' (255) in 'f\010\377oob64\0' out of range 0..64 [372] */
 	/* expect+7: warning: bit field end 263 in 'f\010\377oob64\0' out of range 0..64 [373] */
 	snprintb(buf, sizeof(buf),
Index: src/tests/usr.bin/xlint/lint1/msg_373.c
diff -u src/tests/usr.bin/xlint/lint1/msg_373.c:1.1 src/tests/usr.bin/xlint/lint1/msg_373.c:1.2
--- src/tests/usr.bin/xlint/lint1/msg_373.c:1.1	Fri Mar  1 19:39:29 2024
+++ src/tests/usr.bin/xlint/lint1/msg_373.c	Sat Mar  2 11:56:37 2024
@@ -1,4 +1,4 @@
-/*	$NetBSD: msg_373.c,v 1.1 2024/03/01 19:39:29 rillig Exp $	*/
+/*	$NetBSD: msg_373.c,v 1.2 2024/03/02 11:56:37 rillig Exp $	*/
 # 3 "msg_373.c"
 
 // Test for message: bit field end %ju in '%.*s' out of range 0..64 [373]
@@ -21,9 +21,10 @@ example(uint64_t u64)
 {
 	char buf[64];
 
-	/* expect+11: warning: field width '\101' (65) in 'f\000\101all+1\0' out of range 0..64 [372] */
-	/* expect+10: warning: bit field end 65 in 'f\000\101all+1\0' out of range 0..64 [373] */
-	/* expect+9: warning: bit field end 65 in 'f\001\100oob64\0' out of range 0..64 [373] */
+	/* expect+12: warning: field width '\101' (65) in 'f\000\101all+1\0' out of range 0..64 [372] */
+	/* expect+11: warning: bit field end 65 in 'f\000\101all+1\0' out of range 0..64 [373] */
+	/* expect+10: warning: bit field end 65 in 'f\001\100oob64\0' out of range 0..64 [373] */
+	/* expect+9: warning: 'f\001\100oob64\0' overlaps earlier 'f\000\100all\0' on bit 1 [376] */
 	/* expect+8: warning: field width '\377' (255) in 'f\010\377oob64\0' out of range 0..64 [372] */
 	/* expect+7: warning: bit field end 263 in 'f\010\377oob64\0' out of range 0..64 [373] */
 	snprintb(buf, sizeof(buf),
Index: src/tests/usr.bin/xlint/lint1/msg_375.c
diff -u src/tests/usr.bin/xlint/lint1/msg_375.c:1.1 src/tests/usr.bin/xlint/lint1/msg_375.c:1.2
--- src/tests/usr.bin/xlint/lint1/msg_375.c:1.1	Fri Mar  1 19:39:29 2024
+++ src/tests/usr.bin/xlint/lint1/msg_375.c	Sat Mar  2 11:56:37 2024
@@ -1,4 +1,4 @@
-/*	$NetBSD: msg_375.c,v 1.1 2024/03/01 19:39:29 rillig Exp $	*/
+/*	$NetBSD: msg_375.c,v 1.2 2024/03/02 11:56:37 rillig Exp $	*/
 # 3 "msg_375.c"
 
 // Test for message: comparison value '%.*s' (%ju) exceeds field width %ju [375]
@@ -30,7 +30,7 @@ example(uint64_t u64)
 		"=\01715\0"
 		"=\02016\0"
 		"=\37716\0"
-	    "F\000\004low\0"
+	    "F\004\004low\0"
 		":\01715\0"
 		":\02016\0"
 		":\37716\0",

Index: src/tests/usr.bin/xlint/lint1/t_usage.sh
diff -u src/tests/usr.bin/xlint/lint1/t_usage.sh:1.16 src/tests/usr.bin/xlint/lint1/t_usage.sh:1.17
--- src/tests/usr.bin/xlint/lint1/t_usage.sh:1.16	Fri Mar  1 19:39:29 2024
+++ src/tests/usr.bin/xlint/lint1/t_usage.sh	Sat Mar  2 11:56:37 2024
@@ -1,4 +1,4 @@
-# $NetBSD: t_usage.sh,v 1.16 2024/03/01 19:39:29 rillig Exp $
+# $NetBSD: t_usage.sh,v 1.17 2024/03/02 11:56:37 rillig Exp $
 #
 # Copyright (c) 2023 The NetBSD Foundation, Inc.
 # All rights reserved.
@@ -39,13 +39,13 @@ suppress_messages_body()
 
 	# The largest known message.
 	atf_check \
-	    "$lint1" -X 375 code.c /dev/null
+	    "$lint1" -X 376 code.c /dev/null
 
 	# Larger than the largest known message.
 	atf_check \
 	    -s 'exit:1' \
-	    -e "inline:lint1: invalid message ID '376'\n" \
-	    "$lint1" -X 376 code.c /dev/null
+	    -e "inline:lint1: invalid message ID '377'\n" \
+	    "$lint1" -X 377 code.c /dev/null
 
 	# Whitespace is not allowed before a message ID.
 	atf_check \

Index: src/usr.bin/xlint/lint1/cksnprintb.c
diff -u src/usr.bin/xlint/lint1/cksnprintb.c:1.2 src/usr.bin/xlint/lint1/cksnprintb.c:1.3
--- src/usr.bin/xlint/lint1/cksnprintb.c:1.2	Fri Mar  1 21:52:48 2024
+++ src/usr.bin/xlint/lint1/cksnprintb.c	Sat Mar  2 11:56:37 2024
@@ -1,4 +1,4 @@
-/*	$NetBSD: cksnprintb.c,v 1.2 2024/03/01 21:52:48 rillig Exp $	*/
+/*	$NetBSD: cksnprintb.c,v 1.3 2024/03/02 11:56:37 rillig Exp $	*/
 
 /*-
  * Copyright (c) 2024 The NetBSD Foundation, Inc.
@@ -35,7 +35,7 @@
 
 #include <sys/cdefs.h>
 #if defined(__RCSID)
-__RCSID("$NetBSD: cksnprintb.c,v 1.2 2024/03/01 21:52:48 rillig Exp $");
+__RCSID("$NetBSD: cksnprintb.c,v 1.3 2024/03/02 11:56:37 rillig Exp $");
 #endif
 
 #include <stdbool.h>
@@ -43,6 +43,15 @@ __RCSID("$NetBSD: cksnprintb.c,v 1.2 202
 
 #include "lint1.h"
 
+typedef struct {
+	bool new_style;
+	const buffer *fmt;
+	uint64_t field_width;
+	uint64_t covered;
+	size_t covered_start[64];
+	size_t covered_end[64];
+} checker;
+
 static bool
 match_string_literal(const tnode_t *tn, const buffer **str)
 {
@@ -120,9 +129,44 @@ check_hex_escape(const buffer *buf, quot
 		warning(358, len(it), start(it, buf));
 }
 
+static void
+check_overlap(checker *ck, uint64_t dir_lsb, uint64_t width,
+	      size_t start, size_t end)
+{
+	if (dir_lsb >= 64 || width == 0 || width > 64)
+		return;
+	unsigned lsb = (unsigned)(ck->new_style ? dir_lsb : dir_lsb - 1);
+
+	uint64_t field_mask = value_bits((unsigned)width) << lsb;
+	uint64_t overlap = ck->covered & field_mask;
+	if (overlap == 0)
+		goto done;
+
+	for (unsigned i = lsb; i < 64; i++) {
+		if (!(overlap & bit(i)))
+			continue;
+		/* '%.*s' overlaps earlier '%.*s' on bit %u */
+		warning(376,
+		    (int)(end - start), ck->fmt->data + start,
+		    (int)(ck->covered_end[i] - ck->covered_start[i]),
+		    ck->fmt->data + ck->covered_start[i],
+		    ck->new_style ? i : i + 1);
+		break;
+	}
+
+done:
+	ck->covered |= field_mask;
+	for (unsigned i = lsb; i < 64; i++) {
+		if (field_mask & bit(i)) {
+			ck->covered_start[i] = start;
+			ck->covered_end[i] = end;
+		}
+	}
+}
+
 static bool
 check_directive(const buffer *fmt, quoted_iterator *it, bool new_style,
-		uint64_t *prev_field_width)
+		checker *ck)
 {
 
 	if (!quoted_next(fmt, it))
@@ -242,11 +286,15 @@ check_directive(const buffer *fmt, quote
 		warning(373, val(bit) + val(width),
 		    range(dir, *it), start(dir, fmt));
 	}
-	if (has_cmp && *prev_field_width < 64
-	    && cmp.value & ~(uint64_t)0 << *prev_field_width) {
+	if (has_cmp && ck->field_width < 64
+	    && cmp.value & ~(uint64_t)0 << ck->field_width) {
 		/* comparison value '%.*s' (%ju) exceeds field width %ju */
 		warning(375, len(cmp), start(cmp, fmt), val(cmp),
-		    *prev_field_width);
+		    (uintmax_t)ck->field_width);
+	}
+	if (has_bit) {
+		uint64_t w = has_width ? width.value : 1;
+		check_overlap(ck, bit.value, w, dir.start, it->i);
 	}
 	if (descr.i == prev.i && dir.value != 'F') {
 		/* empty description in '%.*s' */
@@ -254,7 +302,7 @@ check_directive(const buffer *fmt, quote
 	}
 
 	if (has_width)
-		*prev_field_width = width.value;
+		ck->field_width = width.value;
 	return true;
 }
 
@@ -284,7 +332,11 @@ check_snprintb(const tnode_t *expr)
 		return;
 	}
 
-	uint64_t prev_field_width = 64;
-	while (check_directive(fmt, &it, new_style, &prev_field_width))
+	checker ck = {
+		.new_style = new_style,
+		.fmt = fmt,
+		.field_width = 64,
+	};
+	while (check_directive(fmt, &it, new_style, &ck))
 		continue;
 }

Index: src/usr.bin/xlint/lint1/err.c
diff -u src/usr.bin/xlint/lint1/err.c:1.227 src/usr.bin/xlint/lint1/err.c:1.228
--- src/usr.bin/xlint/lint1/err.c:1.227	Sat Mar  2 09:32:18 2024
+++ src/usr.bin/xlint/lint1/err.c	Sat Mar  2 11:56:37 2024
@@ -1,4 +1,4 @@
-/*	$NetBSD: err.c,v 1.227 2024/03/02 09:32:18 rillig Exp $	*/
+/*	$NetBSD: err.c,v 1.228 2024/03/02 11:56:37 rillig Exp $	*/
 
 /*
  * Copyright (c) 1994, 1995 Jochen Pohl
@@ -37,7 +37,7 @@
 
 #include <sys/cdefs.h>
 #if defined(__RCSID)
-__RCSID("$NetBSD: err.c,v 1.227 2024/03/02 09:32:18 rillig Exp $");
+__RCSID("$NetBSD: err.c,v 1.228 2024/03/02 11:56:37 rillig Exp $");
 #endif
 
 #include <limits.h>
@@ -431,6 +431,7 @@ static const char *const msgs[] = {
 	"bit field end %ju in '%.*s' out of range 0..64",		// 373
 	"unknown directive '%.*s'",					// 374
 	"comparison value '%.*s' (%ju) exceeds field width %ju",	// 375
+	"'%.*s' overlaps earlier '%.*s' on bit %u",			// 376
 };
 
 static bool is_suppressed[sizeof(msgs) / sizeof(msgs[0])];

Added files:

Index: src/tests/usr.bin/xlint/lint1/msg_376.c
diff -u /dev/null src/tests/usr.bin/xlint/lint1/msg_376.c:1.1
--- /dev/null	Sat Mar  2 11:56:37 2024
+++ src/tests/usr.bin/xlint/lint1/msg_376.c	Sat Mar  2 11:56:37 2024
@@ -0,0 +1,56 @@
+/*	$NetBSD: msg_376.c,v 1.1 2024/03/02 11:56:37 rillig Exp $	*/
+# 3 "msg_376.c"
+
+// Test for message: '%.*s' overlaps earlier '%.*s' on bit %u [376]
+
+/*
+ * When bits and fields overlap, it's often due to typos or off-by-one errors.
+ */
+
+/* lint1-extra-flags: -X 351 */
+
+typedef typeof(sizeof(0)) size_t;
+typedef unsigned long long uint64_t;
+
+int snprintb(char*, size_t, const char*, uint64_t);
+
+void
+example(unsigned u32, uint64_t u64)
+{
+	char buf[64];
+
+	// In the old-style format, bit positions are 1-based.
+	/* expect+10: warning: '\x01lsb' overlaps earlier '\001lsb' on bit 1 [376] */
+	/* expect+9: warning: '\x20msb""\041oob""\x21oob' overlaps earlier '\040msb' on bit 32 [376] */
+	snprintb(buf, sizeof(buf),
+	    "\020"
+	    "\001lsb"
+	    "\x01lsb"
+	    "\040msb"
+	    "\x20msb"
+	    "\041oob"
+	    "\x21oob",
+	    u32);
+
+	// In the new-style format, bit positions are 1-based.
+	/* expect+10: warning: 'b\x00lsb\0' overlaps earlier 'b\000lsb\0' on bit 0 [376] */
+	/* expect+9: warning: 'b\x3fmsb\0' overlaps earlier 'b\077msb\0' on bit 63 [376] */
+	/* expect+8: warning: bit position '\x40' (64) in 'b\x40oob\0' out of range 0..63 [371] */
+	snprintb(buf, sizeof(buf),
+	    "\177\020"
+	    "b\000lsb\0"
+	    "b\x00lsb\0"
+	    "b\077msb\0"
+	    "b\x3fmsb\0"
+	    "b\x40oob\0",
+	    u64);
+
+	/* expect+7: warning: 'F\014\010f2\0' overlaps earlier 'f\010\010f1\0' on bit 12 [376] */
+	/* expect+6: warning: 'f\020\010f3\0' overlaps earlier 'F\014\010f2\0' on bit 16 [376] */
+	snprintb(buf, sizeof(buf),
+	    "\177\020"
+	    "f\010\010f1\0"
+	    "F\014\010f2\0"
+	    "f\020\010f3\0",
+	    u64);
+}

Reply via email to