Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions libc/docs/dev/printf_behavior.rst
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ conversions (%r, %k); any fixed point number conversion will be treated as
invalid. This reduces code size. This has no effect if the current compiler does
not support fixed point numbers.

LIBC_COPT_PRINTF_DISABLE_WIDE
--------------------------------
When set, this flag disables support for wide characters (%lc and %ls). Any
conversions will be ignored. This reduces code size. This will be set by default
on windows platforms as current printf implementation does not support UTF-16 wide
characters.

LIBC_COPT_PRINTF_NO_NULLPTR_CHECKS
----------------------------------
When set, this flag disables the nullptr checks in %n and %s.
Expand Down
16 changes: 16 additions & 0 deletions libc/src/stdio/printf_core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ if(NOT TARGET ${target_error_mapper})
set(target_error_mapper libc.src.stdio.printf_core.generic.error_mapper)
endif()

set(wchar_deps libc.src.__support.wchar.wcrtomb)
set(wchar_compile_flags "")

if(${LIBC_TARGET_OS} STREQUAL "windows" OR WIN32)
set(wchar_deps "")
list(APPEND wchar_compile_flags "-DLIBC_COPT_PRINTF_DISABLE_WIDE")
endif()

add_header_library(
printf_config
HDRS
Expand All @@ -67,6 +75,7 @@ add_header_library(
parser.h
DEPENDS
.core_structs
libc.hdr.types.wint_t
libc.src.__support.arg_list
libc.src.__support.ctype_utils
libc.src.__support.str_to_integer
Expand Down Expand Up @@ -106,11 +115,16 @@ add_header_library(
float_dec_converter.h
fixed_converter.h #TODO: Check if this should be disabled when fixed unavail
strerror_converter.h
COMPILE_OPTIONS
${wchar_compile_flags}
DEPENDS
.core_structs
.printf_config
.writer
libc.include.inttypes
libc.hdr.types.wchar_t
libc.hdr.types.wint_t
libc.hdr.wchar_macros
libc.src.__support.big_int
libc.src.__support.common
libc.src.__support.CPP.limits
Expand All @@ -123,8 +137,10 @@ add_header_library(
libc.src.__support.integer_to_string
libc.src.__support.libc_assert
libc.src.__support.uint128
libc.src.__support.wchar.mbstate
libc.src.__support.StringUtil.error_to_string
libc.src.string.memory_utils.inline_memcpy
${wchar_deps}
)

add_header_library(
Expand Down
34 changes: 32 additions & 2 deletions libc/src/stdio/printf_core/char_converter.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//===-- String Converter for printf -----------------------------*- C++ -*-===//
//===-- Character 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.
Expand All @@ -9,6 +9,15 @@
#ifndef LLVM_LIBC_SRC_STDIO_PRINTF_CORE_CHAR_CONVERTER_H
#define LLVM_LIBC_SRC_STDIO_PRINTF_CORE_CHAR_CONVERTER_H

#include "hdr/types/wchar_t.h"
#include "hdr/types/wint_t.h"
#include "hdr/wchar_macros.h"
#include "src/__support/wchar/mbstate.h"

#ifndef LIBC_COPT_PRINTF_DISABLE_WIDE
#include "src/__support/wchar/wcrtomb.h"
#endif // LIBC_COPT_PRINTF_DISABLE_WIDE

#include "src/__support/macros/config.h"
#include "src/stdio/printf_core/converter_utils.h"
#include "src/stdio/printf_core/core_structs.h"
Expand All @@ -33,7 +42,28 @@ LIBC_INLINE int convert_char(Writer<write_mode> *writer,
RET_IF_RESULT_NEGATIVE(writer->write(' ', padding_spaces));
}

RET_IF_RESULT_NEGATIVE(writer->write(c));
if (to_conv.length_modifier == LengthModifier::l) {
#ifndef LIBC_COPT_PRINTF_DISABLE_WIDE
wint_t wi = static_cast<wint_t>(to_conv.conv_val_raw);

if (wi == WEOF) {
return -1;
}

char mb_str[MB_LEN_MAX];
internal::mbstate mbstate;
wchar_t wc = static_cast<wchar_t>(wi);

auto ret = internal::wcrtomb(mb_str, wc, &mbstate);
if (!ret.has_value()) {
return -1;
}

RET_IF_RESULT_NEGATIVE(writer->write({mb_str, ret.value()}));
#endif // LIBC_COPT_PRINTF_DISABLE_WIDE
} else {
RET_IF_RESULT_NEGATIVE(writer->write(c));
}

// If the padding is on the right side, write the spaces last.
if (padding_spaces > 0 &&
Expand Down
13 changes: 9 additions & 4 deletions libc/src/stdio/printf_core/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#ifndef LLVM_LIBC_SRC_STDIO_PRINTF_CORE_PARSER_H
#define LLVM_LIBC_SRC_STDIO_PRINTF_CORE_PARSER_H

#include "hdr/types/wint_t.h"
#include "include/llvm-libc-macros/stdfix-macros.h"
#include "src/__support/CPP/algorithm.h" // max
#include "src/__support/CPP/limits.h"
Expand Down Expand Up @@ -73,9 +74,9 @@ template <typename ArgProvider> class Parser {
ArgProvider args_cur;

#ifndef LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
// args_start stores the start of the va_args, which is allows getting the
// value of arguments that have already been passed. args_index is tracked so
// that we know which argument args_cur is on.
// args_start stores the start of the va_args, which helps in getting the
// number of arguments that have already been passed. args_index is tracked
// so that we know which argument args_cur is on.
ArgProvider args_start;
size_t args_index = 1;

Expand Down Expand Up @@ -173,7 +174,11 @@ template <typename ArgProvider> class Parser {
section.has_conv = true;
break;
case ('c'):
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, int, conv_index);
if (section.length_modifier == LengthModifier::l) {
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, wint_t, conv_index);
} else {
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, int, conv_index);
}
break;
case ('d'):
case ('i'):
Expand Down
9 changes: 9 additions & 0 deletions libc/test/src/stdio/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ if(LIBC_CONF_PRINTF_DISABLE_STRERROR)
list(APPEND sprintf_test_copts "-DLIBC_COPT_PRINTF_DISABLE_STRERROR")
endif()

set(snprintf_test_flags "")
if(${LIBC_TARGET_OS} STREQUAL "windows" OR WIN32)
list(APPEND snprintf_test_flags "-DLIBC_COPT_PRINTF_DISABLE_WIDE")
endif()

add_fp_unittest(
sprintf_test
UNIT_TEST_ONLY
Expand All @@ -158,7 +163,11 @@ add_libc_test(
libc_stdio_unittests
SRCS
snprintf_test.cpp
COMPILE_OPTIONS
${snprintf_test_flags}
DEPENDS
libc.hdr.types.wchar_t
libc.hdr.wchar_macros
libc.src.stdio.snprintf
)

Expand Down
12 changes: 12 additions & 0 deletions libc/test/src/stdio/printf_core/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
set(printf_test_flags "")
if(${LIBC_TARGET_OS} STREQUAL "windows" OR WIN32)
list(APPEND printf_test_flags "-DLIBC_COPT_PRINTF_DISABLE_WIDE")
endif()

add_libc_unittest(
parser_test
SUITE
Expand All @@ -6,7 +11,10 @@ add_libc_unittest(
parser_test.cpp
LINK_LIBRARIES
LibcPrintfHelpers
COMPILE_OPTIONS
${printf_test_flags}
DEPENDS
libc.hdr.types.wchar_t
libc.src.stdio.printf_core.parser
libc.src.stdio.printf_core.core_structs
libc.src.__support.CPP.string_view
Expand All @@ -19,6 +27,8 @@ add_libc_unittest(
libc_stdio_unittests
SRCS
writer_test.cpp
COMPILE_OPTIONS
${printf_test_flags}
DEPENDS
libc.src.stdio.printf_core.writer
libc.src.string.memory_utils.inline_memcpy
Expand All @@ -31,6 +41,8 @@ add_libc_unittest(
libc_stdio_unittests
SRCS
converter_test.cpp
COMPILE_OPTIONS
${printf_test_flags}
DEPENDS
libc.src.stdio.printf_core.converter
libc.src.stdio.printf_core.writer
Expand Down
19 changes: 19 additions & 0 deletions libc/test/src/stdio/printf_core/parser_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//
//===----------------------------------------------------------------------===//

#include "hdr/types/wchar_t.h"
#include "src/__support/CPP/bit.h"
#include "src/__support/CPP/string_view.h"
#include "src/__support/arg_list.h"
Expand Down Expand Up @@ -370,6 +371,24 @@ TEST(LlvmLibcPrintfParserTest,
ASSERT_PFORMAT_EQ(expected, format_arr[0]);
}

TEST(LlvmLibcPrintfParserTest, EvalOneArgWithWideCharacter) {
LIBC_NAMESPACE::printf_core::FormatSection format_arr[2];
const char *str = "%lc";
wchar_t arg1 = L'€';
evaluate(format_arr, str, arg1);

LIBC_NAMESPACE::printf_core::FormatSection expected;
expected.has_conv = true;

expected.raw_string = {str, 3};
expected.length_modifier = LIBC_NAMESPACE::printf_core::LengthModifier::l;
expected.conv_val_raw =
static_cast<LIBC_NAMESPACE::fputil::FPBits<double>::StorageType>(arg1);
expected.conv_name = 'c';

ASSERT_PFORMAT_EQ(expected, format_arr[0]);
}

#ifndef LIBC_COPT_PRINTF_DISABLE_INDEX_MODE

TEST(LlvmLibcPrintfParserTest, IndexModeOneArg) {
Expand Down
54 changes: 54 additions & 0 deletions libc/test/src/stdio/snprintf_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
//
//===----------------------------------------------------------------------===//

#include "hdr/types/wchar_t.h"
#include "hdr/wchar_macros.h"
#include "src/stdio/snprintf.h"

#include "test/UnitTest/ErrnoCheckingTest.h"
Expand Down Expand Up @@ -74,3 +76,55 @@ TEST(LlvmLibcSNPrintfTest, CharsWrittenOverflow) {
EXPECT_LT(written, 0);
ASSERT_ERRNO_FAILURE();
}

#ifndef LIBC_COPT_PRINTF_DISABLE_WIDE
TEST(LlvmLibcSNPrintfTest, WideCharConversion) {
char buff[16];
int written;

written = LIBC_NAMESPACE::snprintf(buff, sizeof(buff), "%lc",
static_cast<wchar_t>(L'€'));
EXPECT_EQ(written, 3);
ASSERT_STREQ(buff, "€");
}

TEST(LlvmLibcSNPrintfTest, WideCharConversionLeftJustified) {
char buff[16];
int written;

written = LIBC_NAMESPACE::snprintf(buff, sizeof(buff), "%-4lc",
static_cast<wchar_t>(L'€'));
EXPECT_EQ(written, 6);
ASSERT_STREQ(buff, "€ ");
}

TEST(LlvmLibcSNPrintfTest, WideCharConversionRightJustified) {
char buff[16];
int written;

written = LIBC_NAMESPACE::snprintf(buff, sizeof(buff), "%4lc",
static_cast<wchar_t>(L'€'));
EXPECT_EQ(written, 6);
ASSERT_STREQ(buff, " €");
}

TEST(LlvmLibcSNPrintfTest, WideCharWEOFConversion) {
char buff[16];
int written;

written = LIBC_NAMESPACE::snprintf(buff, sizeof(buff), "%lc",
static_cast<wchar_t>(WEOF));
EXPECT_EQ(written, -1);
ASSERT_ERRNO_FAILURE();
}

TEST(LlvmLibcSNPrintfTest, WideCharInvalidConversion) {
char buff[16];
int written;

written = LIBC_NAMESPACE::snprintf(buff, sizeof(buff), "%lc",
static_cast<wchar_t>(0x12ffff));
EXPECT_EQ(written, -1);
ASSERT_ERRNO_FAILURE();
}
#endif // LIBC_COPT_PRINTF_DISABLE_WIDE
Loading