[libc-commits] [libc] [libc] implement getwchar (PR #196164)

Michael Jones via libc-commits libc-commits at lists.llvm.org
Wed May 6 12:58:04 PDT 2026


https://github.com/michaelrj-google created https://github.com/llvm/llvm-project/pull/196164

Add getwchar function and tests. Part 8/11. All build file changes are in
part 11.

Assisted by Gemini


>From a5a202a0e54ceba6920a13367059a630fe5e7807 Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Wed, 6 May 2026 18:28:19 +0000
Subject: [PATCH] [libc] implement getwchar

Add getwchar function and tests. Part 8/11. All build file changes are in
part 11.

Assisted by Gemini
---
 libc/src/wchar/getwchar.cpp           |  33 +++++
 libc/src/wchar/getwchar.h             |  21 +++
 libc/test/src/wchar/getwchar_test.cpp | 183 ++++++++++++++++++++++++++
 3 files changed, 237 insertions(+)
 create mode 100644 libc/src/wchar/getwchar.cpp
 create mode 100644 libc/src/wchar/getwchar.h
 create mode 100644 libc/test/src/wchar/getwchar_test.cpp

diff --git a/libc/src/wchar/getwchar.cpp b/libc/src/wchar/getwchar.cpp
new file mode 100644
index 0000000000000..7adc6f18a3440
--- /dev/null
+++ b/libc/src/wchar/getwchar.cpp
@@ -0,0 +1,33 @@
+//===-- Implementation of getwchar ----------------------------------------===//
+//
+// 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/getwchar.h"
+#include "hdr/types/FILE.h"
+#include "hdr/types/wint_t.h"
+#include "hdr/wchar_macros.h" // For WEOF
+#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/stdio/stdin.h" // For stdin
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(wint_t, getwchar, ()) {
+  auto *f = reinterpret_cast<File *>(LIBC_NAMESPACE::stdin);
+  wchar_t wc;
+  FileIOResult result = f->read(&wc, 1);
+  if (result.has_error() || result.value < 1) {
+    if (result.has_error())
+      libc_errno = result.error;
+    return WEOF;
+  }
+  return static_cast<wint_t>(wc);
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/wchar/getwchar.h b/libc/src/wchar/getwchar.h
new file mode 100644
index 0000000000000..0e04b60f900fd
--- /dev/null
+++ b/libc/src/wchar/getwchar.h
@@ -0,0 +1,21 @@
+//===-- Implementation header for getwchar --------------------------------===//
+//
+// 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_GETWCHAR_H
+#define LLVM_LIBC_SRC_WCHAR_GETWCHAR_H
+
+#include "hdr/types/wint_t.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+wint_t getwchar();
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_WCHAR_GETWCHAR_H
diff --git a/libc/test/src/wchar/getwchar_test.cpp b/libc/test/src/wchar/getwchar_test.cpp
new file mode 100644
index 0000000000000..8439f7a14df55
--- /dev/null
+++ b/libc/test/src/wchar/getwchar_test.cpp
@@ -0,0 +1,183 @@
+//===-- Unittests for getwchar --------------------------------------------===//
+//
+// 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/wchar_macros.h" // For WEOF
+#include "src/stdio/fclose.h"
+#include "src/stdio/feof.h"
+#include "src/stdio/ferror.h"
+#include "src/stdio/fopen.h"
+#include "src/stdio/fwrite.h"
+#include "src/stdio/stdin.h"
+#include "src/wchar/fwide.h"
+#include "src/wchar/getwchar.h"
+#include "src/wchar/ungetwc.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/Test.h"
+
+using LlvmLibcGetwcharTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+
+// TODO: Analyze if redirecting stdin here works, and also if it's the right way
+// to approach this.
+
+TEST_F(LlvmLibcGetwcharTest, ReadValidWideCharacters) {
+  auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("getwchar_valid.test"));
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
+  ASSERT_FALSE(file == nullptr);
+
+  // Write "12"
+  constexpr char CONTENT[] = "12";
+  ASSERT_EQ(LIBC_NAMESPACE::fwrite(CONTENT, 1, sizeof(CONTENT) - 1, file),
+            sizeof(CONTENT) - 1);
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+
+  // Redirect stdin
+  ::FILE *original_stdin = LIBC_NAMESPACE::stdin;
+  LIBC_NAMESPACE::stdin = LIBC_NAMESPACE::fopen(FILENAME, "r");
+  ASSERT_FALSE(LIBC_NAMESPACE::stdin == nullptr);
+
+  // Verify unoriented
+  EXPECT_EQ(LIBC_NAMESPACE::fwide(LIBC_NAMESPACE::stdin, 0), 0);
+
+  // Read characters
+  EXPECT_EQ(LIBC_NAMESPACE::getwchar(), static_cast<wint_t>(L'1'));
+  EXPECT_EQ(LIBC_NAMESPACE::getwchar(), static_cast<wint_t>(L'2'));
+
+  // Orientation should be wide
+  EXPECT_GT(LIBC_NAMESPACE::fwide(LIBC_NAMESPACE::stdin, 0), 0);
+
+  // Restore stdin
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(LIBC_NAMESPACE::stdin), 0);
+  LIBC_NAMESPACE::stdin = original_stdin;
+}
+
+TEST_F(LlvmLibcGetwcharTest, ReadUtf8) {
+  auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("getwchar_utf8.test"));
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
+  ASSERT_FALSE(file == nullptr);
+
+  // Write "a¢€𐍈"
+  constexpr unsigned char CONTENT[] = {0x61, 0xC2, 0xA2, 0xE2, 0x82,
+                                       0xAC, 0xF0, 0x90, 0x8D, 0x88};
+  ASSERT_EQ(LIBC_NAMESPACE::fwrite(CONTENT, 1, sizeof(CONTENT), file),
+            sizeof(CONTENT));
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+
+  // Redirect stdin
+  ::FILE *original_stdin = LIBC_NAMESPACE::stdin;
+  LIBC_NAMESPACE::stdin = LIBC_NAMESPACE::fopen(FILENAME, "r");
+  ASSERT_FALSE(LIBC_NAMESPACE::stdin == nullptr);
+
+  EXPECT_EQ(LIBC_NAMESPACE::getwchar(), static_cast<wint_t>(L'a'));
+  EXPECT_EQ(LIBC_NAMESPACE::getwchar(), static_cast<wint_t>(L'¢'));
+  EXPECT_EQ(LIBC_NAMESPACE::getwchar(), static_cast<wint_t>(L'€'));
+  EXPECT_EQ(LIBC_NAMESPACE::getwchar(), static_cast<wint_t>(L'𐍈'));
+
+  // Restore stdin
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(LIBC_NAMESPACE::stdin), 0);
+  LIBC_NAMESPACE::stdin = original_stdin;
+}
+
+TEST_F(LlvmLibcGetwcharTest, EndOfFileBehavior) {
+  auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("getwchar_eof.test"));
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
+  ASSERT_FALSE(file == nullptr);
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+
+  // Redirect stdin
+  ::FILE *original_stdin = LIBC_NAMESPACE::stdin;
+  LIBC_NAMESPACE::stdin = LIBC_NAMESPACE::fopen(FILENAME, "r");
+  ASSERT_FALSE(LIBC_NAMESPACE::stdin == nullptr);
+
+  // Read past EOF
+  EXPECT_EQ(LIBC_NAMESPACE::getwchar(), static_cast<wint_t>(WEOF));
+  EXPECT_NE(LIBC_NAMESPACE::feof(LIBC_NAMESPACE::stdin), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::ferror(LIBC_NAMESPACE::stdin), 0);
+
+  // Restore stdin
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(LIBC_NAMESPACE::stdin), 0);
+  LIBC_NAMESPACE::stdin = original_stdin;
+}
+
+TEST_F(LlvmLibcGetwcharTest, ReadError) {
+  auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("getwchar_readerr.test"));
+
+  // Redirect stdin using a write-only stream
+  ::FILE *original_stdin = LIBC_NAMESPACE::stdin;
+  LIBC_NAMESPACE::stdin = LIBC_NAMESPACE::fopen(FILENAME, "w");
+  ASSERT_FALSE(LIBC_NAMESPACE::stdin == nullptr);
+
+  // Try to read from write-only stream
+  ASSERT_ERRNO_SUCCESS();
+  EXPECT_EQ(LIBC_NAMESPACE::getwchar(), static_cast<wint_t>(WEOF));
+  ASSERT_ERRNO_EQ(EBADF);
+  EXPECT_NE(LIBC_NAMESPACE::ferror(LIBC_NAMESPACE::stdin), 0);
+
+  // Restore stdin
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(LIBC_NAMESPACE::stdin), 0);
+  LIBC_NAMESPACE::stdin = original_stdin;
+}
+
+TEST_F(LlvmLibcGetwcharTest, ByteOrientedMisuse) {
+  auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("getwchar_bytemode.test"));
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w+");
+  ASSERT_FALSE(file == nullptr);
+
+  // Redirect stdin
+  ::FILE *original_stdin = LIBC_NAMESPACE::stdin;
+  LIBC_NAMESPACE::stdin = file;
+
+  // Orient to byte mode
+  EXPECT_LT(LIBC_NAMESPACE::fwide(LIBC_NAMESPACE::stdin, -1), 0);
+
+  // Reading should fail and set errno to EINVAL
+  EXPECT_EQ(LIBC_NAMESPACE::getwchar(), static_cast<wint_t>(WEOF));
+  ASSERT_ERRNO_EQ(EINVAL);
+  EXPECT_NE(LIBC_NAMESPACE::ferror(LIBC_NAMESPACE::stdin), 0);
+
+  // Restore stdin
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(LIBC_NAMESPACE::stdin), 0);
+  LIBC_NAMESPACE::stdin = original_stdin;
+}
+
+TEST_F(LlvmLibcGetwcharTest, InteractionWithUngetwc) {
+  auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("getwchar_unget.test"));
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
+  ASSERT_FALSE(file == nullptr);
+
+  // Write "1"
+  constexpr char CONTENT[] = "1";
+  ASSERT_EQ(LIBC_NAMESPACE::fwrite(CONTENT, 1, sizeof(CONTENT) - 1, file),
+            sizeof(CONTENT) - 1);
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+
+  // Redirect stdin
+  ::FILE *original_stdin = LIBC_NAMESPACE::stdin;
+  LIBC_NAMESPACE::stdin = LIBC_NAMESPACE::fopen(FILENAME, "r");
+  ASSERT_FALSE(LIBC_NAMESPACE::stdin == nullptr);
+
+  // Read '1'
+  EXPECT_EQ(LIBC_NAMESPACE::getwchar(), static_cast<wint_t>(L'1'));
+
+  // Push back 'X'
+  EXPECT_EQ(LIBC_NAMESPACE::ungetwc(L'X', LIBC_NAMESPACE::stdin),
+            static_cast<wint_t>(L'X'));
+
+  // Read again -> should be 'X'
+  EXPECT_EQ(LIBC_NAMESPACE::getwchar(), static_cast<wint_t>(L'X'));
+
+  // Restore stdin
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(LIBC_NAMESPACE::stdin), 0);
+  LIBC_NAMESPACE::stdin = original_stdin;
+}



More information about the libc-commits mailing list