[libc-commits] [libc] [libc] implement fputws (PR #196160)
Michael Jones via libc-commits
libc-commits at lists.llvm.org
Wed May 6 12:54:08 PDT 2026
https://github.com/michaelrj-google created https://github.com/llvm/llvm-project/pull/196160
Add fputws function and tests. Part 4/11. All build file changes are in
part 11.
>From a380ae9afeba37a4d258529781e677d123425174 Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Wed, 6 May 2026 18:27:31 +0000
Subject: [PATCH] [libc] implement fputws
Add fputws function and tests. Part 4/11. All build file changes are in
part 11.
---
libc/src/wchar/fputws.cpp | 38 +++++++
libc/src/wchar/fputws.h | 21 ++++
libc/test/src/wchar/fputws_test.cpp | 151 ++++++++++++++++++++++++++++
3 files changed, 210 insertions(+)
create mode 100644 libc/src/wchar/fputws.cpp
create mode 100644 libc/src/wchar/fputws.h
create mode 100644 libc/test/src/wchar/fputws_test.cpp
diff --git a/libc/src/wchar/fputws.cpp b/libc/src/wchar/fputws.cpp
new file mode 100644
index 0000000000000..c3927a9f4c76c
--- /dev/null
+++ b/libc/src/wchar/fputws.cpp
@@ -0,0 +1,38 @@
+//===-- Implementation of fputws ------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/wchar/fputws.h"
+#include "hdr/types/FILE.h"
+#include "src/__support/File/file.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
+#include "src/string/string_length.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, fputws,
+ (const wchar_t *__restrict str, ::FILE *__restrict stream)) {
+ LIBC_CRASH_ON_NULLPTR(str);
+ LIBC_CRASH_ON_NULLPTR(stream);
+
+ size_t len = internal::string_length(str);
+
+ auto *f = reinterpret_cast<File *>(stream);
+ FileIOResult result = f->write(str, len);
+ if (result.has_error() || result.value < len) {
+ if (result.has_error())
+ libc_errno = result.error;
+ return -1;
+ }
+
+ return static_cast<int>(result.value);
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/wchar/fputws.h b/libc/src/wchar/fputws.h
new file mode 100644
index 0000000000000..8e4e57e78cc77
--- /dev/null
+++ b/libc/src/wchar/fputws.h
@@ -0,0 +1,21 @@
+//===-- Implementation header for fputws ----------------------------------===//
+//
+// 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_WCHAR_FPUTWS_H
+#define LLVM_LIBC_SRC_WCHAR_FPUTWS_H
+
+#include "hdr/types/FILE.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+int fputws(const wchar_t *__restrict str, ::FILE *__restrict stream);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_WCHAR_FPUTWS_H
diff --git a/libc/test/src/wchar/fputws_test.cpp b/libc/test/src/wchar/fputws_test.cpp
new file mode 100644
index 0000000000000..4e62a40c41724
--- /dev/null
+++ b/libc/test/src/wchar/fputws_test.cpp
@@ -0,0 +1,151 @@
+//===-- Unittests for fputws ----------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "hdr/errno_macros.h"
+#include "src/stdio/fclose.h"
+#include "src/stdio/ferror.h"
+#include "src/stdio/fopen.h"
+#include "src/stdio/fread.h"
+#include "src/wchar/fputws.h"
+#include "src/wchar/fwide.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/Test.h"
+
+using LlvmLibcFputwsTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+
+TEST_F(LlvmLibcFputwsTest, WriteWideString) {
+ auto FILENAME =
+ libc_make_test_file_path(APPEND_LIBC_TEST("fputws_string.test"));
+ ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
+ ASSERT_FALSE(file == nullptr);
+
+ // String with 1, 2, 3, and 4-byte UTF-8 characters
+ // Hello, ¢€𐍈 world!\n
+ constexpr wchar_t STR[] = L"Hello, ¢€𐍈 world!\n";
+ EXPECT_GE(LIBC_NAMESPACE::fputws(STR, file), 0);
+
+ ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+
+ // Open to read raw bytes and verify UTF-8 mapping
+ file = LIBC_NAMESPACE::fopen(FILENAME, "r");
+ ASSERT_FALSE(file == nullptr);
+
+ unsigned char buffer[50] = {0};
+ // Expecting 7 (Hello, ) + 2 (¢) + 3 (€) + 4 (𐍈) + 8 ( world!\n) = 24 bytes
+ ASSERT_EQ(LIBC_NAMESPACE::fread(buffer, 1, 24, file), size_t(24));
+
+ // "Hello, "
+ EXPECT_EQ(buffer[0], static_cast<unsigned char>('H'));
+ EXPECT_EQ(buffer[1], static_cast<unsigned char>('e'));
+ EXPECT_EQ(buffer[2], static_cast<unsigned char>('l'));
+ EXPECT_EQ(buffer[3], static_cast<unsigned char>('l'));
+ EXPECT_EQ(buffer[4], static_cast<unsigned char>('o'));
+ EXPECT_EQ(buffer[5], static_cast<unsigned char>(','));
+ EXPECT_EQ(buffer[6], static_cast<unsigned char>(' '));
+
+ // "¢"
+ EXPECT_EQ(buffer[7], static_cast<unsigned char>(0xC2));
+ EXPECT_EQ(buffer[8], static_cast<unsigned char>(0xA2));
+
+ // "€"
+ EXPECT_EQ(buffer[9], static_cast<unsigned char>(0xE2));
+ EXPECT_EQ(buffer[10], static_cast<unsigned char>(0x82));
+ EXPECT_EQ(buffer[11], static_cast<unsigned char>(0xAC));
+
+ // "𐍈"
+ EXPECT_EQ(buffer[12], static_cast<unsigned char>(0xF0));
+ EXPECT_EQ(buffer[13], static_cast<unsigned char>(0x90));
+ EXPECT_EQ(buffer[14], static_cast<unsigned char>(0x8D));
+ EXPECT_EQ(buffer[15], static_cast<unsigned char>(0x88));
+
+ // " world!\n"
+ EXPECT_EQ(buffer[16], static_cast<unsigned char>(' '));
+ EXPECT_EQ(buffer[17], static_cast<unsigned char>('w'));
+ EXPECT_EQ(buffer[18], static_cast<unsigned char>('o'));
+ EXPECT_EQ(buffer[19], static_cast<unsigned char>('r'));
+ EXPECT_EQ(buffer[20], static_cast<unsigned char>('l'));
+ EXPECT_EQ(buffer[21], static_cast<unsigned char>('d'));
+ EXPECT_EQ(buffer[22], static_cast<unsigned char>('!'));
+ EXPECT_EQ(buffer[23], static_cast<unsigned char>('\n'));
+
+ ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+}
+
+TEST_F(LlvmLibcFputwsTest, EmptyString) {
+ auto FILENAME =
+ libc_make_test_file_path(APPEND_LIBC_TEST("fputws_empty.test"));
+ ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
+ ASSERT_FALSE(file == nullptr);
+
+ EXPECT_GE(LIBC_NAMESPACE::fputws(L"", file), 0);
+
+ ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+
+ // Verify nothing was written
+ file = LIBC_NAMESPACE::fopen(FILENAME, "r");
+ ASSERT_FALSE(file == nullptr);
+
+ unsigned char buffer[5] = {0};
+ ASSERT_EQ(LIBC_NAMESPACE::fread(buffer, 1, 5, file), size_t(0));
+
+ ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+}
+
+TEST_F(LlvmLibcFputwsTest, InvalidStream) {
+ auto FILENAME =
+ libc_make_test_file_path(APPEND_LIBC_TEST("fputws_invalid.test"));
+
+ // Create the file first
+ ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
+ ASSERT_FALSE(file == nullptr);
+ ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+
+ // Open in read-only mode
+ file = LIBC_NAMESPACE::fopen(FILENAME, "r");
+ ASSERT_FALSE(file == nullptr);
+
+ // Try to write to read-only file
+ ASSERT_ERRNO_SUCCESS();
+ EXPECT_LT(LIBC_NAMESPACE::fputws(L"fail", file), 0);
+ ASSERT_ERRNO_EQ(EBADF);
+
+ ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+}
+
+TEST_F(LlvmLibcFputwsTest, EncodingErrorEILSEQ) {
+ auto FILENAME =
+ libc_make_test_file_path(APPEND_LIBC_TEST("fputws_eilseq.test"));
+ ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
+ ASSERT_FALSE(file == nullptr);
+
+ // String with invalid wide character point (outside Unicode range) which
+ // fails encoding
+ constexpr wchar_t STR[] = {static_cast<wchar_t>(0x110000), L'\0'};
+ ASSERT_ERRNO_SUCCESS();
+ EXPECT_LT(LIBC_NAMESPACE::fputws(STR, file), 0);
+ ASSERT_ERRNO_EQ(EILSEQ);
+ EXPECT_NE(LIBC_NAMESPACE::ferror(file), 0);
+
+ ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+}
+
+TEST_F(LlvmLibcFputwsTest, ByteModeFailure) {
+ auto FILENAME =
+ libc_make_test_file_path(APPEND_LIBC_TEST("fputws_bytemode.test"));
+ ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w+");
+ ASSERT_FALSE(file == nullptr);
+
+ // Orient to byte mode
+ EXPECT_LT(LIBC_NAMESPACE::fwide(file, -1), 0);
+
+ // Writing wide string should fail and set errno to EINVAL
+ EXPECT_LT(LIBC_NAMESPACE::fputws(L"fail", file), 0);
+ ASSERT_ERRNO_EQ(EINVAL);
+
+ ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+}
More information about the libc-commits
mailing list