Skip to content

Commit

Permalink
[libc] add printf oct conversion
Browse files Browse the repository at this point in the history
The oct converter handles the %o conversion.

Reviewed By: lntue

Differential Revision: https://reviews.llvm.org/D127985
  • Loading branch information
michaelrj-google committed Jun 27, 2022
1 parent 85eaecb commit 6ec465a
Show file tree
Hide file tree
Showing 6 changed files with 266 additions and 16 deletions.
1 change: 1 addition & 0 deletions libc/src/stdio/printf_core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ add_object_library(
int_converter.h
hex_converter.h
ptr_converter.h
oct_converter.h
DEPENDS
.writer
.core_structs
Expand Down
2 changes: 1 addition & 1 deletion libc/src/stdio/printf_core/converter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ int convert(Writer *writer, const FormatSection &to_conv) {
case 'u':
return convert_int(writer, to_conv);
case 'o':
// return convert_oct(writer, to_conv);
return convert_oct(writer, to_conv);
case 'x':
case 'X':
return convert_hex(writer, to_conv);
Expand Down
1 change: 1 addition & 0 deletions libc/src/stdio/printf_core/converter_atlas.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "src/stdio/printf_core/int_converter.h"

// defines convert_oct
#include "src/stdio/printf_core/oct_converter.h"
// defines convert_hex
#include "src/stdio/printf_core/hex_converter.h"

Expand Down
111 changes: 111 additions & 0 deletions libc/src/stdio/printf_core/oct_converter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//===-- Octal Converter for printf ------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC_STDIO_PRINTF_CORE_OCT_CONVERTER_H
#define LLVM_LIBC_SRC_STDIO_PRINTF_CORE_OCT_CONVERTER_H

#include "src/stdio/printf_core/converter_utils.h"
#include "src/stdio/printf_core/core_structs.h"
#include "src/stdio/printf_core/writer.h"

#include <inttypes.h>
#include <stddef.h>

namespace __llvm_libc {
namespace printf_core {

int inline convert_oct(Writer *writer, const FormatSection &to_conv) {
// This is the number of digits it takes to represent a octal value of a
// certain number of bits. Each oct digit represents 3 bits, so the value is
// ceil(number of bits / 3).
constexpr size_t BUFF_LEN = ((sizeof(uintmax_t) * 8) + 2) / 3;
uintmax_t num = to_conv.conv_val_raw;
char buffer[BUFF_LEN];

num = apply_length_modifier(num, to_conv.length_modifier);

// Since the buffer is size to sized to be able fit the entire number, buf_cur
// can never reach 0. So, we do not need bounds checking on buf_cur.
size_t buff_cur = BUFF_LEN;
for (; num > 0 /* && buff_cur > 0 */; --buff_cur, num /= 8)
buffer[buff_cur - 1] = (num % 8) + '0';

size_t num_digits = BUFF_LEN - buff_cur;

// These are signed to prevent underflow due to negative values. Negative
// values are treated the same as 0.
int zeroes;
int spaces;

// Negative precision indicates that it was not specified.
if (to_conv.precision < 0) {
if ((to_conv.flags &
(FormatFlags::LEADING_ZEROES | FormatFlags::LEFT_JUSTIFIED)) ==
FormatFlags::LEADING_ZEROES) {
// If this conv has flag 0 but not - and no specified precision, it's
// padded with 0's instead of spaces identically to if precision =
// min_width. For example: ("%04o", 15) -> "0017"
zeroes = to_conv.min_width - num_digits;
spaces = 0;
} else if (num_digits < 1) {
// If no precision is specified, precision defaults to 1. This means that
// if the integer passed to the conversion is 0, a 0 will be printed.
// Example: ("%3o", 0) -> " 0"
zeroes = 1;
spaces = to_conv.min_width - zeroes;
} else {
// If there are enough digits to pass over the precision, just write the
// number, padded by spaces.
zeroes = 0;
spaces = to_conv.min_width - num_digits;
}
} else {
// If precision was specified, possibly write zeroes, and possibly write
// spaces. Example: ("%5.4o", 010000) -> "10000"
// If the check for if zeroes is negative was not there, spaces would be
// incorrectly evaluated as 1.
zeroes = to_conv.precision - num_digits; // a negative value means 0
if (zeroes < 0)
zeroes = 0;
spaces = to_conv.min_width - zeroes - num_digits;
}

// The alternate form prefix is "0", so it's handled by increasing the number
// of zeroes if necessary.
if (((to_conv.flags & FormatFlags::ALTERNATE_FORM) ==
FormatFlags::ALTERNATE_FORM) &&
zeroes < 1) {
zeroes = 1;
--spaces;
}

if ((to_conv.flags & FormatFlags::LEFT_JUSTIFIED) ==
FormatFlags::LEFT_JUSTIFIED) {
// If left justified the pattern is zeroes digits spaces
if (zeroes > 0)
RET_IF_RESULT_NEGATIVE(writer->write_chars('0', zeroes));
if (num_digits > 0)
RET_IF_RESULT_NEGATIVE(writer->write(buffer + buff_cur, num_digits));
if (spaces > 0)
RET_IF_RESULT_NEGATIVE(writer->write_chars(' ', spaces));
} else {
// Else the pattern is spaces zeroes digits
if (spaces > 0)
RET_IF_RESULT_NEGATIVE(writer->write_chars(' ', spaces));
if (zeroes > 0)
RET_IF_RESULT_NEGATIVE(writer->write_chars('0', zeroes));
if (num_digits > 0)
RET_IF_RESULT_NEGATIVE(writer->write(buffer + buff_cur, num_digits));
}
return 0;
}

} // namespace printf_core
} // namespace __llvm_libc

#endif // LLVM_LIBC_SRC_STDIO_PRINTF_CORE_OCT_CONVERTER_H
46 changes: 31 additions & 15 deletions libc/test/src/stdio/printf_core/converter_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,28 +196,24 @@ TEST_F(LlvmLibcPrintfConverterTest, IntConversionSimple) {
ASSERT_EQ(writer.get_chars_written(), 5);
}

// This needs to be switched to the new testing layout, but that's still in the
// int patch so I need to land that first. This is what I get for not keeping my
// patches small and focused.
TEST(LlvmLibcPrintfConverterTest, HexConversion) {
char str[20];
__llvm_libc::printf_core::StringWriter str_writer(str);
__llvm_libc::printf_core::Writer writer(
reinterpret_cast<void *>(&str_writer),
__llvm_libc::printf_core::write_to_string);

__llvm_libc::printf_core::FormatSection left_justified_conv;
left_justified_conv.has_conv = true;
left_justified_conv.raw_string = "%#018x";
left_justified_conv.raw_len = 6;
left_justified_conv.conv_name = 'x';
left_justified_conv.flags =
static_cast<__llvm_libc::printf_core::FormatFlags>(
__llvm_libc::printf_core::FormatFlags::ALTERNATE_FORM |
__llvm_libc::printf_core::FormatFlags::LEADING_ZEROES);
left_justified_conv.min_width = 18;
left_justified_conv.conv_val_raw = 0x123456ab;
__llvm_libc::printf_core::convert(&writer, left_justified_conv);
__llvm_libc::printf_core::FormatSection section;
section.has_conv = true;
section.raw_string = "%#018x";
section.raw_len = 6;
section.conv_name = 'x';
section.flags = static_cast<__llvm_libc::printf_core::FormatFlags>(
__llvm_libc::printf_core::FormatFlags::ALTERNATE_FORM |
__llvm_libc::printf_core::FormatFlags::LEADING_ZEROES);
section.min_width = 18;
section.conv_val_raw = 0x123456ab;
__llvm_libc::printf_core::convert(&writer, section);

str_writer.terminate();
ASSERT_STREQ(str, "0x00000000123456ab");
Expand All @@ -243,3 +239,23 @@ TEST(LlvmLibcPrintfConverterTest, PointerConversion) {
ASSERT_STREQ(str, "0x123456ab");
ASSERT_EQ(writer.get_chars_written(), 10);
}

TEST(LlvmLibcPrintfConverterTest, OctConversion) {
char str[20];
__llvm_libc::printf_core::StringWriter str_writer(str);
__llvm_libc::printf_core::Writer writer(
reinterpret_cast<void *>(&str_writer),
__llvm_libc::printf_core::write_to_string);

__llvm_libc::printf_core::FormatSection section;
section.has_conv = true;
section.raw_string = "%o";
section.raw_len = 2;
section.conv_name = 'o';
section.conv_val_raw = 01234;
__llvm_libc::printf_core::convert(&writer, section);

str_writer.terminate();
ASSERT_STREQ(str, "1234");
ASSERT_EQ(writer.get_chars_written(), 4);
}
121 changes: 121 additions & 0 deletions libc/test/src/stdio/sprintf_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,127 @@ TEST(LlvmLibcSPrintfTest, PointerConv) {
EXPECT_GT(written, 0);
}

TEST(LlvmLibcSPrintfTest, OctConv) {
char buff[64];
int written;

// Basic Tests.

written = __llvm_libc::sprintf(buff, "%o", 01234);
EXPECT_EQ(written, 4);
ASSERT_STREQ(buff, "1234");

written = __llvm_libc::sprintf(buff, "%o", 04567);
EXPECT_EQ(written, 4);
ASSERT_STREQ(buff, "4567");

// Length Modifier Tests.

written = __llvm_libc::sprintf(buff, "%hho", 0401);
EXPECT_EQ(written, 1);
ASSERT_STREQ(buff, "1");

written = __llvm_libc::sprintf(buff, "%llo", 01777777777777777777777ull);
EXPECT_EQ(written, 22);
ASSERT_STREQ(buff, "1777777777777777777777"); // ull max

written = __llvm_libc::sprintf(buff, "%to", ~ptrdiff_t(0));
if (sizeof(ptrdiff_t) == 8) {
EXPECT_EQ(written, 22);
ASSERT_STREQ(buff, "1777777777777777777777");
} else if (sizeof(ptrdiff_t) == 4) {
EXPECT_EQ(written, 11);
ASSERT_STREQ(buff, "37777777777");
}

// Min Width Tests.

written = __llvm_libc::sprintf(buff, "%4o", 0701);
EXPECT_EQ(written, 4);
ASSERT_STREQ(buff, " 701");

written = __llvm_libc::sprintf(buff, "%2o", 0107);
EXPECT_EQ(written, 3);
ASSERT_STREQ(buff, "107");

// Precision Tests.

written = __llvm_libc::sprintf(buff, "%o", 0);
EXPECT_EQ(written, 1);
ASSERT_STREQ(buff, "0");

written = __llvm_libc::sprintf(buff, "%.0o", 0);
EXPECT_EQ(written, 0);
ASSERT_STREQ(buff, "");

written = __llvm_libc::sprintf(buff, "%.5o", 0153);
EXPECT_EQ(written, 5);
ASSERT_STREQ(buff, "00153");

written = __llvm_libc::sprintf(buff, "%.2o", 0135);
EXPECT_EQ(written, 3);
ASSERT_STREQ(buff, "135");

// Flag Tests.

written = __llvm_libc::sprintf(buff, "%-5o", 0246);
EXPECT_EQ(written, 5);
ASSERT_STREQ(buff, "246 ");

written = __llvm_libc::sprintf(buff, "%#o", 0234);
EXPECT_EQ(written, 4);
ASSERT_STREQ(buff, "0234");

written = __llvm_libc::sprintf(buff, "%05o", 0470);
EXPECT_EQ(written, 5);
ASSERT_STREQ(buff, "00470");

written = __llvm_libc::sprintf(buff, "%0#6o", 0753);
EXPECT_EQ(written, 6);
ASSERT_STREQ(buff, "000753");

written = __llvm_libc::sprintf(buff, "%-#6o", 0642);
EXPECT_EQ(written, 6);
ASSERT_STREQ(buff, "0642 ");

// Combined Tests.

written = __llvm_libc::sprintf(buff, "%#-07o", 0703);
EXPECT_EQ(written, 7);
ASSERT_STREQ(buff, "0703 ");

written = __llvm_libc::sprintf(buff, "%7.5o", 0314);
EXPECT_EQ(written, 7);
ASSERT_STREQ(buff, " 00314");

written = __llvm_libc::sprintf(buff, "%#9.5o", 0234);
EXPECT_EQ(written, 9);
ASSERT_STREQ(buff, " 00234");

written = __llvm_libc::sprintf(buff, "%-7.5o", 0260);
EXPECT_EQ(written, 7);
ASSERT_STREQ(buff, "00260 ");

written = __llvm_libc::sprintf(buff, "%5.4o", 010000);
EXPECT_EQ(written, 5);
ASSERT_STREQ(buff, "10000");

// Multiple Conversion Tests.

written = __llvm_libc::sprintf(buff, "%10o %-#10o", 0456, 0123);
EXPECT_EQ(written, 21);
ASSERT_STREQ(buff, " 456 0123 ");

written = __llvm_libc::sprintf(buff, "%-5.4o%#.4o", 075, 025);
EXPECT_EQ(written, 9);
ASSERT_STREQ(buff, "0075 0025");

written = __llvm_libc::sprintf(buff, "%04hho %#.5llo %-6.3zo", 256 + 077,
01000000000000ll, size_t(2));
EXPECT_EQ(written, 26);
ASSERT_STREQ(buff, "0077 01000000000000 002 ");
}

#ifndef LLVM_LIBC_PRINTF_DISABLE_INDEX_MODE
TEST(LlvmLibcSPrintfTest, IndexModeParsing) {
char buff[64];
Expand Down

0 comments on commit 6ec465a

Please sign in to comment.