off_t madness

Christian Biere <[email protected]>
Newsgroups gmane.os.netbsd.devel.general
Message-ID <20070205181747.GC16601@cyclonus>
Hi,

the following shows what can wrong if off_t is used incorrectly in comparisons.
I'm not sure whether it really yields a warning on 64-bit archs (like AMD64),
it does for me if I fake a 64-bit size_t on a 32-bit arch. Regardless of this
warning, things can still go wrong as shown below.

May I assume option 2 is the preferred way to write code like this?

Technically, I think the compiler warning is wrong because off_t is implicitely
promoted to a non-existing unsigned integer type (uint64_t in our case) or if
size_t was wider, it would be converted to size_t. Thus, this looks like a
false-positive to me. Careless use of explicit casts here can cause real bugs
though (i.e., off_t should never be cast to size_t unless you've already
checked that it fits).

#include <inttypes.h>
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>

#define MAX_INT_VAL_STEP(t) \
	((t) 1 << (CHAR_BIT * sizeof(t) - 1 - ((t) -1 < 1)))
	 
#define MAX_INT_VAL(t) \
	((MAX_INT_VAL_STEP(t) - 1) + MAX_INT_VAL_STEP(t))

#define MIN_INT_VAL(t) \
	((t) -MAX_INT_VAL(t) - 1)

/* uint64_t for 64-bit arch, uint32_t for 32-bit arch */
typedef uint32_t my_size_t;

#define MY_SIZE_T_MAX MAX_INT_VAL(my_size_t)
#define OFF_T_MAX MAX_INT_VAL(off_t)

int main(void)
{
	my_size_t bufsize;
	off_t filesize;
	int ret;

	bufsize = -1;
	filesize = OFF_T_MAX;

	/*
	 * Question: 
	 *
	 * Does the file fit into the buffer? (example: mmap())
	 *
	 * ret = filesize >= 0 && filesize <= bufsize;
	 *
	 * Problem:
	 *
	 * - comparison between signed and unsigned.
	 *
	 * - off_t is a signed integer type, an equivalent unsigned integer
	 *   type (unsigned off_t?) does not exist.
	 *
	 * - size_t is architecture-dependent.
	 *
	 * - off_t is currently always int64_t on BSDs but not everywhere
	 *   (int32_t on many legacy systems) and may change (int128_t?).
	 *
	 * - OFF_T_MAX (maximum value of off_t) is unknown but can be
	 *   inferred making a few assumptions that should be true in
	 *   practice.
	 */

	/* Option 1 */

	ret = OFF_T_MAX > MY_SIZE_T_MAX
		? (filesize >= 0 && filesize <= (off_t)bufsize)
		: (filesize >= 0 && (my_size_t)filesize <= bufsize);

	/*
	 * WRONG if OFF_T_MAX is wrong.
	 */

	printf("1) filesize >= 0 && filesize <= bufsize: %s\n",
		ret ? "yes" : "no");

	/* Option 2 */

	ret = filesize >= 0
		&& (uintmax_t)(intmax_t)filesize <= (uintmax_t)bufsize;

	/*
	 * WRONG if off_t is an extended integer type not covered by
	 * intmax_t. This is probably rather unlikely, so it might be
	 * the preferrable solution for ISO C99 code.
	 *
	 * example:
	 *
	 * typedef int64_t intmax_t;
	 * typedef __int128_t off_t;
	 */

	printf("2) filesize >= 0 && filesize <= bufsize: %s\n",
		ret ? "yes" : "no");

	/* Option 3 */

	ret = filesize > 0 && filesize <= (off_t)bufsize;

	/*
	 * WRONG if OFF_T_MAX < SIZE_MAX
	 *
	 * example:
	 *
	 * typedef int64_t off_t;
	 * typedef uint64_t size_t;
	 *
	 * or:
	 * typedef int32_t off_t;
	 * typedef uint64_t size_t;
	 *
	 */

	printf("3) filesize >= 0 && filesize <= bufsize: %s\n",
		ret ? "yes" : "no");

	/* Option 4 */

	ret = filesize >= 0 && (size_t)filesize <= bufsize;

	/*
	 * WRONG if SIZE_MAX < OFF_T_MAX
	 *
	 * example:
	 *
	 * typedef int64_t off_t;
	 * typedef uint64_t size_t;
	 *
	 * or:
	 * typedef int64_t off_t;
	 * typedef uint32_t size_t;
	 */

	printf("4) filesize >= 0 && filesize <= bufsize: %s\n",
		ret ? "yes" : "no");

	/* Option 5 */

	ret = filesize >= 0 && filesize <= bufsize;

	/*
	 * Just ignore the compiler warning.
	 */

	printf("5) filesize >= 0 && filesize <= bufsize: %s\n",
		ret ? "yes" : "no");

	return 0;
}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.