[libc-commits] [libc] [libc] implement fgetws (PR #196161)

Michael Jones via libc-commits libc-commits at lists.llvm.org
Mon May 18 15:49:50 PDT 2026


https://github.com/michaelrj-google updated https://github.com/llvm/llvm-project/pull/196161

>From b89fc8aaee918730e6da9be93e6d69826621010f Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Wed, 6 May 2026 18:27:43 +0000
Subject: [PATCH 1/5] [libc] implement fgetws

Add fgetws function and tests. Part 5/11. All build file changes are in
part 11.

Assisted by Gemini
---
 libc/src/stdio/generic/fgets.cpp    |   2 +
 libc/src/wchar/fgetws.cpp           |  63 ++++++++++
 libc/src/wchar/fgetws.h             |  21 ++++
 libc/test/src/wchar/fgetws_test.cpp | 181 ++++++++++++++++++++++++++++
 4 files changed, 267 insertions(+)
 create mode 100644 libc/src/wchar/fgetws.cpp
 create mode 100644 libc/src/wchar/fgetws.h
 create mode 100644 libc/test/src/wchar/fgetws_test.cpp

diff --git a/libc/src/stdio/generic/fgets.cpp b/libc/src/stdio/generic/fgets.cpp
index 33d469c620ca2..e3de77b5dc281 100644
--- a/libc/src/stdio/generic/fgets.cpp
+++ b/libc/src/stdio/generic/fgets.cpp
@@ -29,6 +29,8 @@ LLVM_LIBC_FUNCTION(char *, fgets,
   // i is an int because it's frequently compared to count, which is also int.
   int i = 0;
 
+  // TODO: rewrite this logic. If there's an error it should return nullptr. See
+  // fgetws for example.
   for (; i < (count - 1) && c != '\n'; ++i) {
     auto result = stream->read_unlocked(&c, 1);
     size_t r = result.value;
diff --git a/libc/src/wchar/fgetws.cpp b/libc/src/wchar/fgetws.cpp
new file mode 100644
index 0000000000000..97031f9af144f
--- /dev/null
+++ b/libc/src/wchar/fgetws.cpp
@@ -0,0 +1,63 @@
+//===-- Implementation of fgetws ------------------------------------------===//
+//
+// 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/fgetws.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"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(wchar_t *, fgetws,
+                   (wchar_t *__restrict ws, int count,
+                    ::FILE *__restrict stream)) {
+  if (count <= 0)
+    return nullptr;
+
+  LIBC_CRASH_ON_NULLPTR(ws);
+  LIBC_CRASH_ON_NULLPTR(stream);
+
+  auto *f = reinterpret_cast<File *>(stream);
+
+  if (count == 1) {
+    ws[0] = L'\0';
+    return ws;
+  }
+
+  f->lock();
+
+  wchar_t *result = ws;
+  int chars_read = 0;
+  wchar_t c = L'\0';
+
+  for (; chars_read < count - 1 && c != '\n'; ++chars_read) {
+    auto read_res = f->read_unlocked(&c, 1);
+    if (read_res.has_error()) {
+      libc_errno = read_res.error;
+      result = nullptr;
+      break;
+    }
+    if (read_res.value < 1) {
+      if (chars_read == 0)
+        result = nullptr;
+      break;
+    }
+    ws[chars_read] = c;
+  }
+
+  if (result != nullptr)
+    ws[chars_read] = L'\0';
+
+  f->unlock();
+  return result;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/wchar/fgetws.h b/libc/src/wchar/fgetws.h
new file mode 100644
index 0000000000000..5fe9572ae25f7
--- /dev/null
+++ b/libc/src/wchar/fgetws.h
@@ -0,0 +1,21 @@
+//===-- Implementation header for fgetws ----------------------------------===//
+//
+// 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_FGETWS_H
+#define LLVM_LIBC_SRC_WCHAR_FGETWS_H
+
+#include "hdr/types/FILE.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+wchar_t *fgetws(wchar_t *__restrict str, int count, ::FILE *__restrict stream);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_WCHAR_FGETWS_H
diff --git a/libc/test/src/wchar/fgetws_test.cpp b/libc/test/src/wchar/fgetws_test.cpp
new file mode 100644
index 0000000000000..5bd6cd7dc9d98
--- /dev/null
+++ b/libc/test/src/wchar/fgetws_test.cpp
@@ -0,0 +1,181 @@
+//===-- Unittests for fgetws ----------------------------------------------===//
+//
+// 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 "hdr/types/wint_t.h"
+#include "src/stdio/fclose.h"
+#include "src/stdio/ferror.h"
+#include "src/stdio/fopen.h"
+#include "src/stdio/fwrite.h"
+#include "src/wchar/fgetws.h"
+#include "src/wchar/fwide.h"
+#include "src/wchar/wcscmp.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/Test.h"
+
+using LlvmLibcFgetwsTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+
+// TODO: Refactor these tests to use standard wide-character string comparison
+// assert macros (e.g., EXPECT_STREQ for wchar_t) once they are supported
+// natively by the LLVM-libc test framework, instead of calling wcscmp.
+TEST_F(LlvmLibcFgetwsTest, ReadWideString) {
+  auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("fgetws_string.test"));
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
+  ASSERT_FALSE(file == nullptr);
+
+  // Write UTF-8 bytes for: "Hello, ¢€𐍈 world!\n"
+  constexpr unsigned char CONTENT[] = {
+      'H',  'e',  'l',  'l',  'o', ',', ' ', 0xC2, 0xA2, 0xE2, 0x82, 0xAC,
+      0xF0, 0x90, 0x8D, 0x88, ' ', 'w', 'o', 'r',  'l',  'd',  '!',  '\n'};
+  ASSERT_EQ(LIBC_NAMESPACE::fwrite(CONTENT, 1, sizeof(CONTENT), file),
+            sizeof(CONTENT));
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+
+  // Open for reading
+  file = LIBC_NAMESPACE::fopen(FILENAME, "r");
+  ASSERT_FALSE(file == nullptr);
+
+  wchar_t buffer[50] = {0};
+  wchar_t *result = LIBC_NAMESPACE::fgetws(buffer, 50, file);
+  ASSERT_FALSE(result == nullptr);
+  EXPECT_EQ(result, buffer);
+  EXPECT_EQ(LIBC_NAMESPACE::wcscmp(buffer, L"Hello, ¢€𐍈 world!\n"), 0);
+
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+}
+
+TEST_F(LlvmLibcFgetwsTest, ReadBounded) {
+  auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("fgetws_bounded.test"));
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
+  ASSERT_FALSE(file == nullptr);
+
+  // Write "1234567890"
+  constexpr char CONTENT[] = "1234567890";
+  ASSERT_EQ(LIBC_NAMESPACE::fwrite(CONTENT, 1, sizeof(CONTENT) - 1, file),
+            sizeof(CONTENT) - 1);
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+
+  // Open for reading
+  file = LIBC_NAMESPACE::fopen(FILENAME, "r");
+  ASSERT_FALSE(file == nullptr);
+
+  wchar_t buffer[10] = {0};
+  // Read bounded by count = 5 (4 chars + null terminator)
+  wchar_t *result = LIBC_NAMESPACE::fgetws(buffer, 5, file);
+  ASSERT_FALSE(result == nullptr);
+  EXPECT_EQ(result, buffer);
+  EXPECT_EQ(LIBC_NAMESPACE::wcscmp(buffer, L"1234"), 0);
+
+  // Read bounded by count = 1 (writes only null terminator)
+  wchar_t buffer_one[5] = {L'x', L'y', L'z', L'\0'};
+  wchar_t *result_one = LIBC_NAMESPACE::fgetws(buffer_one, 1, file);
+  ASSERT_FALSE(result_one == nullptr);
+  EXPECT_EQ(result_one, buffer_one);
+  EXPECT_EQ(static_cast<wint_t>(buffer_one[0]), static_cast<wint_t>(L'\0'));
+  EXPECT_EQ(static_cast<wint_t>(buffer_one[1]),
+            static_cast<wint_t>(L'y')); // untouched
+
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+}
+
+TEST_F(LlvmLibcFgetwsTest, NewlineStops) {
+  auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("fgetws_newline.test"));
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
+  ASSERT_FALSE(file == nullptr);
+
+  // Write "abc\ndef"
+  constexpr char CONTENT[] = "abc\ndef";
+  ASSERT_EQ(LIBC_NAMESPACE::fwrite(CONTENT, 1, sizeof(CONTENT) - 1, file),
+            sizeof(CONTENT) - 1);
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+
+  // Open for reading
+  file = LIBC_NAMESPACE::fopen(FILENAME, "r");
+  ASSERT_FALSE(file == nullptr);
+
+  wchar_t buffer[20] = {0};
+  wchar_t *result = LIBC_NAMESPACE::fgetws(buffer, 10, file);
+  ASSERT_FALSE(result == nullptr);
+  EXPECT_EQ(result, buffer);
+  EXPECT_EQ(LIBC_NAMESPACE::wcscmp(buffer, L"abc\n"), 0);
+
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+}
+
+TEST_F(LlvmLibcFgetwsTest, InvalidStream) {
+  auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("fgetws_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 write-only mode
+  file = LIBC_NAMESPACE::fopen(FILENAME, "w");
+  ASSERT_FALSE(file == nullptr);
+
+  // Try to read from write-only stream
+  wchar_t buffer[10] = {0};
+  ASSERT_ERRNO_SUCCESS();
+  EXPECT_EQ(LIBC_NAMESPACE::fgetws(buffer, 5, file),
+            static_cast<wchar_t *>(nullptr));
+  ASSERT_ERRNO_EQ(EBADF);
+  EXPECT_NE(LIBC_NAMESPACE::ferror(file), 0);
+
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+}
+
+TEST_F(LlvmLibcFgetwsTest, EncodingErrorEILSEQ) {
+  auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("fgetws_eilseq.test"));
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
+  ASSERT_FALSE(file == nullptr);
+
+  // Write an invalid UTF-8 sequence: 0x80 (stray continuation byte)
+  constexpr unsigned char CONTENT[] = {0x80};
+  ASSERT_EQ(LIBC_NAMESPACE::fwrite(CONTENT, 1, sizeof(CONTENT), file),
+            sizeof(CONTENT));
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+
+  // Open for reading
+  file = LIBC_NAMESPACE::fopen(FILENAME, "r");
+  ASSERT_FALSE(file == nullptr);
+
+  // Reading invalid sequence should fail with EILSEQ
+  wchar_t buffer[10] = {0};
+  ASSERT_ERRNO_SUCCESS();
+  EXPECT_EQ(LIBC_NAMESPACE::fgetws(buffer, 5, file),
+            static_cast<wchar_t *>(nullptr));
+  ASSERT_ERRNO_EQ(EILSEQ);
+  EXPECT_NE(LIBC_NAMESPACE::ferror(file), 0);
+
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+}
+
+TEST_F(LlvmLibcFgetwsTest, ByteModeFailure) {
+  auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("fgetws_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);
+
+  // Read wide string should fail and set errno to EINVAL
+  wchar_t buffer[10] = {0};
+  EXPECT_EQ(LIBC_NAMESPACE::fgetws(buffer, 5, file),
+            static_cast<wchar_t *>(nullptr));
+  ASSERT_ERRNO_EQ(EINVAL);
+  EXPECT_NE(LIBC_NAMESPACE::ferror(file), 0);
+
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+}

>From 68e2f0ca701dd01935b5a013b3739b98cf9074e3 Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Wed, 13 May 2026 17:07:54 +0000
Subject: [PATCH 2/5] address comments and update entrypoints

---
 libc/config/linux/aarch64/entrypoints.txt | 2 +-
 libc/config/linux/riscv/entrypoints.txt   | 2 +-
 libc/config/linux/x86_64/entrypoints.txt  | 2 +-
 libc/src/wchar/fgetws.cpp                 | 8 +++++++-
 libc/src/wchar/fgetws.h                   | 8 +++++++-
 5 files changed, 17 insertions(+), 5 deletions(-)

diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index c7458ef36c941..5faf0447173a8 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1256,7 +1256,7 @@ if(LLVM_LIBC_FULL_BUILD)
 
     # wchar.h entrypoints
     libc.src.wchar.fgetwc
-    # libc.src.wchar.fgetws
+    libc.src.wchar.fgetws
     # libc.src.wchar.fputwc
     # libc.src.wchar.fputws
     libc.src.wchar.fwide
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index e3336420cbbcc..0c387c2465116 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1390,7 +1390,7 @@ if(LLVM_LIBC_FULL_BUILD)
 
     # wchar.h entrypoints
     libc.src.wchar.fgetwc
-    # libc.src.wchar.fgetws
+    libc.src.wchar.fgetws
     # libc.src.wchar.fputwc
     # libc.src.wchar.fputws
     libc.src.wchar.fwide
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 3c3b0e835429f..f1202d27facf1 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1472,7 +1472,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.wchar.wcsrtombs
     libc.src.wchar.wcsnrtombs
     libc.src.wchar.fgetwc
-    # libc.src.wchar.fgetws
+    libc.src.wchar.fgetws
     # libc.src.wchar.fputwc
     # libc.src.wchar.fputws
     libc.src.wchar.fwide
diff --git a/libc/src/wchar/fgetws.cpp b/libc/src/wchar/fgetws.cpp
index 97031f9af144f..cdabcc981d202 100644
--- a/libc/src/wchar/fgetws.cpp
+++ b/libc/src/wchar/fgetws.cpp
@@ -1,10 +1,16 @@
-//===-- Implementation of fgetws ------------------------------------------===//
+//===----------------------------------------------------------------------===//
 //
 // 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
 //
 //===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file contains the implementation of the fgetws function, which reads a
+/// wide-character string from a file.
+///
+//===----------------------------------------------------------------------===//
 
 #include "src/wchar/fgetws.h"
 #include "hdr/types/FILE.h"
diff --git a/libc/src/wchar/fgetws.h b/libc/src/wchar/fgetws.h
index 5fe9572ae25f7..9c0cb37321786 100644
--- a/libc/src/wchar/fgetws.h
+++ b/libc/src/wchar/fgetws.h
@@ -1,10 +1,16 @@
-//===-- Implementation header for fgetws ----------------------------------===//
+//===----------------------------------------------------------------------===//
 //
 // 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
 //
 //===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file contains the implementation header for the fgetws function, which
+/// reads a wide-character string from a file.
+///
+//===----------------------------------------------------------------------===//
 
 #ifndef LLVM_LIBC_SRC_WCHAR_FGETWS_H
 #define LLVM_LIBC_SRC_WCHAR_FGETWS_H

>From 1bcfa944d36f8c7ff811a51a2fb3f9311dffa302 Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Wed, 13 May 2026 21:13:03 +0000
Subject: [PATCH 3/5] address comments, fixup includes, add guard to test file

---
 libc/src/wchar/fgetws.cpp           |  3 +--
 libc/src/wchar/fgetws.h             |  4 ++--
 libc/test/src/wchar/CMakeLists.txt  |  4 +++-
 libc/test/src/wchar/fgetws_test.cpp | 12 +++++++++++-
 4 files changed, 17 insertions(+), 6 deletions(-)

diff --git a/libc/src/wchar/fgetws.cpp b/libc/src/wchar/fgetws.cpp
index cdabcc981d202..8ec4a2023e64c 100644
--- a/libc/src/wchar/fgetws.cpp
+++ b/libc/src/wchar/fgetws.cpp
@@ -42,9 +42,8 @@ LLVM_LIBC_FUNCTION(wchar_t *, fgetws,
 
   wchar_t *result = ws;
   int chars_read = 0;
-  wchar_t c = L'\0';
 
-  for (; chars_read < count - 1 && c != '\n'; ++chars_read) {
+  for (wchar_t c = L'\0'; chars_read < count - 1 && c != '\n'; ++chars_read) {
     auto read_res = f->read_unlocked(&c, 1);
     if (read_res.has_error()) {
       libc_errno = read_res.error;
diff --git a/libc/src/wchar/fgetws.h b/libc/src/wchar/fgetws.h
index 9c0cb37321786..86faea0395441 100644
--- a/libc/src/wchar/fgetws.h
+++ b/libc/src/wchar/fgetws.h
@@ -7,8 +7,8 @@
 //===----------------------------------------------------------------------===//
 ///
 /// \file
-/// This file contains the implementation header for the fgetws function, which
-/// reads a wide-character string from a file.
+/// This file contains the prototype for the fgetws function, which reads a
+/// wide-character string from a file.
 ///
 //===----------------------------------------------------------------------===//
 
diff --git a/libc/test/src/wchar/CMakeLists.txt b/libc/test/src/wchar/CMakeLists.txt
index 55b9f32e8226b..6c647bf867000 100644
--- a/libc/test/src/wchar/CMakeLists.txt
+++ b/libc/test/src/wchar/CMakeLists.txt
@@ -613,7 +613,9 @@ add_libc_test(
   SRCS
     fgetws_test.cpp
   DEPENDS
-    libc.include.stdio
+    libc.hdr.errno_macros
+    libc.hdr.stdint_proxy
+    libc.hdr.types.wint_t
     libc.src.stdio.fopen
     libc.src.stdio.fclose
     libc.src.stdio.fwrite
diff --git a/libc/test/src/wchar/fgetws_test.cpp b/libc/test/src/wchar/fgetws_test.cpp
index 5bd6cd7dc9d98..8b01fb566c171 100644
--- a/libc/test/src/wchar/fgetws_test.cpp
+++ b/libc/test/src/wchar/fgetws_test.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "hdr/errno_macros.h"
+#include "hdr/stdint_proxy.h"
 #include "hdr/types/wint_t.h"
 #include "src/stdio/fclose.h"
 #include "src/stdio/ferror.h"
@@ -29,10 +30,19 @@ TEST_F(LlvmLibcFgetwsTest, ReadWideString) {
   ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
   ASSERT_FALSE(file == nullptr);
 
+#if WCHAR_MAX > 0xFFFF
   // Write UTF-8 bytes for: "Hello, ¢€𐍈 world!\n"
   constexpr unsigned char CONTENT[] = {
       'H',  'e',  'l',  'l',  'o', ',', ' ', 0xC2, 0xA2, 0xE2, 0x82, 0xAC,
       0xF0, 0x90, 0x8D, 0x88, ' ', 'w', 'o', 'r',  'l',  'd',  '!',  '\n'};
+  constexpr const wchar_t *EXPECTED_STR = L"Hello, ¢€𐍈 world!\n";
+#else
+  // Write UTF-8 bytes for: "Hello, ¢ world!\n"
+  constexpr unsigned char CONTENT[] = {'H', 'e',  'l',  'l', 'o', ',',
+                                       ' ', 0xC2, 0xA2, ' ', 'w', 'o',
+                                       'r', 'l',  'd',  '!', '\n'};
+  constexpr const wchar_t *EXPECTED_STR = L"Hello, ¢ world!\n";
+#endif
   ASSERT_EQ(LIBC_NAMESPACE::fwrite(CONTENT, 1, sizeof(CONTENT), file),
             sizeof(CONTENT));
   ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
@@ -45,7 +55,7 @@ TEST_F(LlvmLibcFgetwsTest, ReadWideString) {
   wchar_t *result = LIBC_NAMESPACE::fgetws(buffer, 50, file);
   ASSERT_FALSE(result == nullptr);
   EXPECT_EQ(result, buffer);
-  EXPECT_EQ(LIBC_NAMESPACE::wcscmp(buffer, L"Hello, ¢€𐍈 world!\n"), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::wcscmp(buffer, EXPECTED_STR), 0);
 
   ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
 }

>From 36fa93cb09d79a10956c31abf78ba7118cf315cb Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Fri, 15 May 2026 21:31:38 +0000
Subject: [PATCH 4/5] fix header and const FILENAME

---
 libc/test/src/wchar/fgetws_test.cpp | 19 ++++++++++++-------
 1 file changed, 12 insertions(+), 7 deletions(-)

diff --git a/libc/test/src/wchar/fgetws_test.cpp b/libc/test/src/wchar/fgetws_test.cpp
index 8b01fb566c171..4a940c7281b6e 100644
--- a/libc/test/src/wchar/fgetws_test.cpp
+++ b/libc/test/src/wchar/fgetws_test.cpp
@@ -1,10 +1,15 @@
-//===-- Unittests for fgetws ----------------------------------------------===//
+//===----------------------------------------------------------------------===//
 //
 // 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
 //
 //===----------------------------------------------------------------------===//
+///
+/// \file
+/// Unittests for fgetws
+///
+//===----------------------------------------------------------------------===//
 
 #include "hdr/errno_macros.h"
 #include "hdr/stdint_proxy.h"
@@ -25,7 +30,7 @@ using LlvmLibcFgetwsTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
 // assert macros (e.g., EXPECT_STREQ for wchar_t) once they are supported
 // natively by the LLVM-libc test framework, instead of calling wcscmp.
 TEST_F(LlvmLibcFgetwsTest, ReadWideString) {
-  auto FILENAME =
+  const auto FILENAME =
       libc_make_test_file_path(APPEND_LIBC_TEST("fgetws_string.test"));
   ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
   ASSERT_FALSE(file == nullptr);
@@ -61,7 +66,7 @@ TEST_F(LlvmLibcFgetwsTest, ReadWideString) {
 }
 
 TEST_F(LlvmLibcFgetwsTest, ReadBounded) {
-  auto FILENAME =
+  const auto FILENAME =
       libc_make_test_file_path(APPEND_LIBC_TEST("fgetws_bounded.test"));
   ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
   ASSERT_FALSE(file == nullptr);
@@ -96,7 +101,7 @@ TEST_F(LlvmLibcFgetwsTest, ReadBounded) {
 }
 
 TEST_F(LlvmLibcFgetwsTest, NewlineStops) {
-  auto FILENAME =
+  const auto FILENAME =
       libc_make_test_file_path(APPEND_LIBC_TEST("fgetws_newline.test"));
   ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
   ASSERT_FALSE(file == nullptr);
@@ -121,7 +126,7 @@ TEST_F(LlvmLibcFgetwsTest, NewlineStops) {
 }
 
 TEST_F(LlvmLibcFgetwsTest, InvalidStream) {
-  auto FILENAME =
+  const auto FILENAME =
       libc_make_test_file_path(APPEND_LIBC_TEST("fgetws_invalid.test"));
 
   // Create the file first
@@ -145,7 +150,7 @@ TEST_F(LlvmLibcFgetwsTest, InvalidStream) {
 }
 
 TEST_F(LlvmLibcFgetwsTest, EncodingErrorEILSEQ) {
-  auto FILENAME =
+  const auto FILENAME =
       libc_make_test_file_path(APPEND_LIBC_TEST("fgetws_eilseq.test"));
   ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
   ASSERT_FALSE(file == nullptr);
@@ -172,7 +177,7 @@ TEST_F(LlvmLibcFgetwsTest, EncodingErrorEILSEQ) {
 }
 
 TEST_F(LlvmLibcFgetwsTest, ByteModeFailure) {
-  auto FILENAME =
+  const auto FILENAME =
       libc_make_test_file_path(APPEND_LIBC_TEST("fgetws_bytemode.test"));
   ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w+");
   ASSERT_FALSE(file == nullptr);

>From a9ff0cb4a1c2a945d5d32f69d2e77cd3ad131a7e Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Mon, 18 May 2026 22:48:14 +0000
Subject: [PATCH 5/5] fix '\n' as wchar

---
 libc/src/wchar/fgetws.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libc/src/wchar/fgetws.cpp b/libc/src/wchar/fgetws.cpp
index 8ec4a2023e64c..b84196bdad63f 100644
--- a/libc/src/wchar/fgetws.cpp
+++ b/libc/src/wchar/fgetws.cpp
@@ -43,7 +43,7 @@ LLVM_LIBC_FUNCTION(wchar_t *, fgetws,
   wchar_t *result = ws;
   int chars_read = 0;
 
-  for (wchar_t c = L'\0'; chars_read < count - 1 && c != '\n'; ++chars_read) {
+  for (wchar_t c = L'\0'; chars_read < count - 1 && c != L'\n'; ++chars_read) {
     auto read_res = f->read_unlocked(&c, 1);
     if (read_res.has_error()) {
       libc_errno = read_res.error;



More information about the libc-commits mailing list