Fix issues 774, 782 and 789 (printf precision bugs) (#790)

The C standard states that, within the context of a printf-family
function, when specifying the precision of a conversion specification:

> A negative precision argument is taken as if the precision were
> omitted.
- Quoth the C Standard, 7.23.6.1. The fprintf function

Cosmopolitan instead treated negative precision arguments as
though they had a value of 0, which was non-conforming. This
change fixes that. Another issue we found relates to:

> For o conversion, it increases the precision, if and only if
> necessary, to force the first digit of the result to be a zero (if
> the value and precision are both 0, a single 0 is printed).
- Quoth the C standard, 7.23.6.1.6. The fprintf function

When printing numbers in their alternative form, with a precision and
with a conversion specifier of o (octal), Cosmopolitan wasn't following
the standard in two ways:

1. When printing a value with a precision that results in 0-padding,
   cosmopolitan would still add an extra 0 even though this should be
   done "if and only if necessary"
2. When printing a value of 0 with a precision of 0, nothing is
   printed, even though the standard specifically states that a single
   0 is printed in this case

This change fixes those issues too. Furthermore, regression tests have
been introduced to ensure Cosmopolitan continues to be conformant
going forward.

Fixes #774 
Fixes #782 
Fixes #789
This commit is contained in:
Gabriel Ravier 2023-03-29 10:11:48 +02:00 committed by GitHub
parent 2f4335e081
commit 7f925e6be9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 33 additions and 4 deletions

View file

@ -16,10 +16,11 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/fmt/fmt.h"
#include "libc/assert.h"
#include "libc/errno.h"
#include "libc/fmt/conv.h"
#include "libc/fmt/fmt.h"
#include "libc/fmt/fmt.internal.h"
#include "libc/fmt/internal.h"
#include "libc/fmt/itoa.h"
@ -255,6 +256,7 @@ _Hide int __fmt(void *fn, void *arg, const char *format, va_list va) {
}
if (prec < 0) {
prec = 0;
flags &= ~FLAGS_PRECISION;
}
// evaluate length field

View file

@ -46,8 +46,8 @@ static int __fmt_ntoa_format(int out(const char *, void *, size_t), void *arg,
}
/* handle hash */
if (flags & FLAGS_HASH) {
if (!(flags & FLAGS_PRECISION) && len &&
((len == prec) || (len == width)) && buf[len - 1] == '0') {
if ((!(flags & FLAGS_PRECISION) || log2base == 3) && len &&
((len >= prec) || (len >= width)) && buf[len - 1] == '0') {
len--;
if (len && (log2base == 4 || log2base == 1) && buf[len - 1] == '0') {
len--;
@ -94,7 +94,9 @@ int __fmt_ntoa2(int out(const char *, void *, size_t), void *arg,
unsigned len, count, digit;
char buf[BUFFER_SIZE];
len = 0;
if (!value) flags &= ~FLAGS_HASH;
/* we check for log2base != 3 because otherwise we'll print nothing for a value of 0 with precision 0 when # mandates that one be printed */
if (!value && log2base != 3)
flags &= ~FLAGS_HASH;
if (value || !(flags & FLAGS_PRECISION)) {
count = 0;
do {

View file

@ -100,11 +100,36 @@ TEST(fmt, x) {
_gc(xasprintf("%#020hX", (short)0xABCDEF)));
}
TEST(fmt, o) {
EXPECT_STREQ("0000000000000037777777634", _gc(xasprintf("%#.25o", -100)));
EXPECT_STREQ("0001777777777777777777634", _gc(xasprintf("%#.25lo", -100L)));
EXPECT_STREQ("0001777777777777777777634", _gc(xasprintf("%#.25llo", -100LL)));
EXPECT_STREQ("0", _gc(xasprintf("%#.o", 0)));
}
TEST(fmt, b) {
EXPECT_STREQ("000010100 ", _gc(xasprintf("%-14.9b", 20)));
}
TEST(fmt, s) {
EXPECT_STREQ("123456", _gc(xasprintf("%4.*s", -5, "123456")));
EXPECT_STREQ("123456789", _gc(xasprintf("%4.*s", -5, "123456789")));
EXPECT_STREQ("12345678901234567890",
_gc(xasprintf("%4.*s", -5, "12345678901234567890")));
EXPECT_STREQ("123456789012345678901234567890",
_gc(xasprintf("%4.*s", -5, "123456789012345678901234567890")));
EXPECT_STREQ(
"1234567890123456789012345678901234567890",
_gc(xasprintf("%4.*s", -5, "1234567890123456789012345678901234567890")));
EXPECT_STREQ(
"12345678901234567890123456789012345678901234567890",
_gc(xasprintf("%4.*s", -5,
"12345678901234567890123456789012345678901234567890")));
EXPECT_STREQ(
"123456789012345678901234567890123456789012345678901234567890",
_gc(xasprintf(
"%4.*s", -5,
"123456789012345678901234567890123456789012345678901234567890")));
EXPECT_STREQ("Wide character output test",
_gc(xasprintf("%S", L"Wide character output test")));
}