[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [bug-gnulib] snprintf fixes in gnulib
From: |
Bruno Haible |
Subject: |
Re: [bug-gnulib] snprintf fixes in gnulib |
Date: |
Fri, 11 Aug 2006 15:42:56 +0200 |
User-agent: |
KMail/1.9.1 |
Paul Eggert wrote:
> The first thing I noticed was this comment about two size_t variables:
>
> if (len > size - 1) /* equivalent to: (size > 0 && len >= size) */
>
> The comment is not correct if size_t promotes to int
This is a border case; you can fix it by adding a cast:
if (len > (size_t)(size - 1))
> and anyway the
> code would be clearer and typically just as fast if it were written if
> (0 < size && size <= len) since compilers typically optimize this into
> a single comparison these days.
No, they don't. Take "gcc-4.1.1 -O2" on this test file:
===================== foo.c =====================
typedef unsigned int size_t;
int single_cond_jump (size_t len, size_t size)
{
if (len > size - 1)
return len;
else
return -1;
}
int two_cond_jumps (size_t len, size_t size)
{
if (size > 0 && len >= size)
return len;
else
return -1;
}
===================== foo.s =======================
.file "foo.c"
.text
.p2align 4,,15
.globl single_cond_jump
.type single_cond_jump, @function
single_cond_jump:
pushl %ebp
movl $-1, %edx
movl %esp, %ebp
movl 12(%ebp), %eax
movl 8(%ebp), %ecx
decl %eax
cmpl %ecx, %eax
jae .L4
movl %ecx, %edx
.L4:
popl %ebp
movl %edx, %eax
ret
.size single_cond_jump, .-single_cond_jump
.p2align 4,,15
.globl two_cond_jumps
.type two_cond_jumps, @function
two_cond_jumps:
pushl %ebp
movl %esp, %ebp
movl 12(%ebp), %edx
testl %edx, %edx
je .L10
movl 8(%ebp), %eax
cmpl %eax, %edx
ja .L10
popl %ebp
ret
.p2align 4,,7
.L10:
popl %ebp
movl $-1, %eax
.p2align 4,,4
ret
.size two_cond_jumps, .-two_cond_jumps
.ident "GCC: (GNU) 4.1.1"
.section .note.GNU-stack,"",@progbits
============================================================
You see the that gcc turns the two comparisons into two conditional jumps.
It is well-known that jumps affect performance a lot on modern CPUs.
gcc converts two comparisons like this to a single conditional jump only
when both bounds are constant. Not when, like here, one of them is a
variable.
> However, when I looked into the surrounding code I noticed that the
> logic was wrong anyway, since it didn't properly copy the generated
> string into the output buffer when the string is too long.
Right, this is a major bug. Thanks for noticing it!
> While we're on the subject, snprintf should
> check for EOVERFLOW, since it's the interface that stupidly tries to
> copy a size_t into an int, so I changed it to do that.
This is not needed. vasnprintf already does this check.
Still one bug remaining: Your new code will crash when passed an str = NULL
argument.
Furthermore I don't like that your memcpy copies more than needed.
The vasnprintf code needs memory in chunks, is not looking for it byte
for byte. Therefore it can happen that output != str and still
0 <= len < size - and then you are copying around more memory than needed.
Simon, ok to apply this?
*** snprintf.c 10 Aug 2006 19:32:38 -0000 1.8
--- snprintf.c 11 Aug 2006 13:43:12 -0000
***************
*** 1,6 ****
/* Formatted output to strings.
Copyright (C) 2004, 2006 Free Software Foundation, Inc.
! Written by Simon Josefsson and Paul Eggert.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
--- 1,6 ----
/* Formatted output to strings.
Copyright (C) 2004, 2006 Free Software Foundation, Inc.
! Written by Simon Josefsson, Paul Eggert, Bruno Haible.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
***************
*** 22,40 ****
#include "snprintf.h"
- #include <errno.h>
- #include <limits.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include "vasnprintf.h"
- /* Some systems, like OSF/1 4.0 and Woe32, don't have EOVERFLOW. */
- #ifndef EOVERFLOW
- # define EOVERFLOW E2BIG
- #endif
-
/* Print formatted output to string STR. Similar to sprintf, but
additional length SIZE limit how much is written into STR. Returns
string length of formatted string (which may be larger than SIZE).
--- 22,33 ----
***************
*** 57,76 ****
if (output != str)
{
! if (size)
{
! memcpy (str, output, size - 1);
! str[size - 1] = '\0';
}
free (output);
}
! if (INT_MAX < len)
! {
! errno = EOVERFLOW;
! return -1;
! }
return len;
}
--- 50,71 ----
if (output != str)
{
! /* output was malloc()ed by the vasnprintf function. */
!
! if (str != NULL && size > 0)
{
! /* Copy contents of output to str. */
! size_t pruned_len = (len >= size ? size - 1 : len);
!
! memcpy (str, output, pruned_len);
! str[pruned_len] = '\0';
}
free (output);
}
! /* No need to test against len > INT_MAX. vasnprintf already handled
! this case. */
return len;
}