Skip to content

Commit

Permalink
Merge pull request #1104 from Qwinci/binary-printf
Browse files Browse the repository at this point in the history
options/ansi: Handle binary specifiers in format strings
  • Loading branch information
mintsuki authored Aug 20, 2024
2 parents 2eded87 + 9b3c2cd commit 295a609
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 1 deletion.
30 changes: 29 additions & 1 deletion options/ansi/generic/stdio-stubs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ struct PrintfAgent {
case 'p': case 's':
frg::do_printf_chars(*_formatter, t, opts, szmod, _vsp);
break;
case 'd': case 'i': case 'o': case 'x': case 'X': case 'u':
case 'd': case 'i': case 'o': case 'x': case 'X': case 'b': case 'B': case 'u':
frg::do_printf_ints(*_formatter, t, opts, szmod, _vsp);
break;
case 'f': case 'F': case 'g': case 'G': case 'e': case 'E':
Expand Down Expand Up @@ -596,6 +596,34 @@ static int do_scanf(H &handler, const char *fmt, __builtin_va_list args) {
store_int(dest, type, res);
break;
}
case 'b': {
unsigned long long res = 0;
char c = handler.look_ahead();
int count = 0;
EOF_CHECK(c == '\0');
if (c == '0') {
handler.consume();
c = handler.look_ahead();
if (c == 'b' || c == 'B') {
handler.consume();
c = handler.look_ahead();
}
}
while (true) {
if (c == '0' || c == '1') {
handler.consume();
res = res * 2 + (c - '0');
} else {
break;
}
count++;
c = handler.look_ahead();
}
NOMATCH_CHECK(count == 0);
if (dest)
store_int(dest, type, res);
break;
}
case 's': {
char c = handler.look_ahead();
EOF_CHECK(c == '\0');
Expand Down
4 changes: 4 additions & 0 deletions options/internal/include/mlibc/strtol.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ Return stringToInteger(const Char *__restrict nptr, Char **__restrict endptr, in

bool hasOctalPrefix = s[0] == widen<Char>('0');
bool hasHexPrefix = hasOctalPrefix && (s[1] == widen<Char>('x') || s[1] == widen<Char>('X'));
bool hasBinPrefix = hasOctalPrefix && (s[1] == widen<Char>('b') || s[1] == widen<Char>('B'));

// There's two tricky cases we need to keep in mind here:
// 1. We should interpret "0x5" as hex 5 rather than octal 0.
Expand All @@ -91,6 +92,9 @@ Return stringToInteger(const Char *__restrict nptr, Char **__restrict endptr, in
if ((base == 0 || base == 16) && hasHexPrefix && char_detail<Char>::isHexDigit(s[2])) {
s += 2;
base = 16;
} else if ((base == 0 || base == 2) && hasBinPrefix) {
s += 2;
base = 2;
} else if ((base == 0 || base == 8) && hasOctalPrefix) {
base = 8;
} else if (base == 0) {
Expand Down
33 changes: 33 additions & 0 deletions tests/ansi/sprintf.c
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,27 @@ int main() {
assert(!strcmp(buf, "0XC"));
sprintf(buf, "%#o", 12);
assert(!strcmp(buf, "014"));

sprintf(buf, "%#x", 0);
assert(!strcmp(buf, "0"));
sprintf(buf, "%#X", 0);
assert(!strcmp(buf, "0"));
sprintf(buf, "%#o", 0);
assert(!strcmp(buf, "0"));

// Disable -Wformat here because the compiler might not know about the b specifier.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat"
sprintf(buf, "%#b", 12);
assert(!strcmp(buf, "0b1100"));
sprintf(buf, "%#B", 12);
assert(!strcmp(buf, "0B1100"));
sprintf(buf, "%#b", 0);
assert(!strcmp(buf, "0"));
sprintf(buf, "%#B", 0);
assert(!strcmp(buf, "0"));
#pragma GCC diagnostic pop

// Test 'd' with different size mods to see
// if they work
sprintf(buf, "%d", 12);
Expand Down Expand Up @@ -177,6 +191,25 @@ int main() {
sprintf(buf, "%hho", (unsigned char) 12);
assert(!strcmp(buf, "14"));

// Disable -Wformat here because the compiler might not know about the b specifier.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat"
// Test 'b' with different size mods to see
// if they work
sprintf(buf, "%b", 12);
assert(!strcmp(buf, "1100"));
sprintf(buf, "%lb", 12L);
assert(!strcmp(buf, "1100"));
sprintf(buf, "%llb", 12LL);
assert(!strcmp(buf, "1100"));
sprintf(buf, "%zb", (size_t)12);
assert(!strcmp(buf, "1100"));
sprintf(buf, "%hb", (unsigned short) 12);
assert(!strcmp(buf, "1100"));
sprintf(buf, "%hhb", (unsigned char) 12);
assert(!strcmp(buf, "1100"));
#pragma GCC diagnostic pop

// Test %n$ syntax.
sprintf(buf, "%1$d", 12);
assert(!strcmp(buf, "12"));
Expand Down
4 changes: 4 additions & 0 deletions tests/ansi/sscanf.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ struct format_test_cases {
{"%u", "0420", 420, T_UINT, 1},
{"%o", "0420", 0420, T_UINT, 1},
{"%x", "0xCB7", 0xCB7, T_UINT, 1},
#ifndef USE_HOST_LIBC
{"%b", "0b1011", 0b1011, T_UINT, 1},
{"%b", "0B1011", 0b1011, T_UINT, 1},
#endif
{"%%", "%", 0, T_NONE, 0},
{"%c", " I am not a fan of this solution.", ' ', T_CHAR, 1},
{" %c", " CBT (capybara therapy)", 'C', T_CHAR, 1},
Expand Down
6 changes: 6 additions & 0 deletions tests/ansi/strtol.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ int main () {
DO_TEST("-1101110100110100100000", -3624224, -1, strtol, 2);
DO_TEST("0x6fffff", 0x6fffff, -1, strtol, 0);
DO_TEST("0666", 0666, -1, strtol, 0);
#ifndef USE_HOST_LIBC
DO_TEST("1100", 0b1100, -1, strtol, 2);
DO_TEST("0b1100", 0b1100, -1, strtol, 0);
DO_TEST("0B1100", 0b1100, -1, strtol, 0);
DO_TEST("0b1zzz", 1, 3, strtol, 0);
#endif
DO_TEST("0xzzz", 0, 1, strtol, 0);
DO_TEST("0yzzz", 0, 1, strtol, 0);
DO_TEST("00xzz", 0, 2, strtol, 0);
Expand Down

0 comments on commit 295a609

Please sign in to comment.