[libc-commits] [libc] [libc] Implement fmemopen (PR #221748)
Jeong Jihyeon via libc-commits
libc-commits at lists.llvm.org
Wed Sep 9 18:01:25 PDT 2026
https://github.com/JihyeonJeong129 updated https://github.com/llvm/llvm-project/pull/221748
>From ae0109e2de869da5596d1a955c8d99736f5aff22 Mon Sep 17 00:00:00 2001
From: Jihyeon Jeong <jh.jeong129 at gmail.com>
Date: Mon, 7 Sep 2026 09:27:26 +0000
Subject: [PATCH 1/3] [libc] Implement fmemopen
---
libc/config/linux/aarch64/entrypoints.txt | 1 +
libc/config/linux/riscv/entrypoints.txt | 1 +
libc/config/linux/x86_64/entrypoints.txt | 1 +
libc/include/stdio.yaml | 8 +
libc/src/stdio/CMakeLists.txt | 22 ++
libc/src/stdio/fmemopen.cpp | 172 +++++++++++++
libc/src/stdio/fmemopen.h | 23 ++
libc/test/src/stdio/CMakeLists.txt | 30 +++
libc/test/src/stdio/fmemopen_test.cpp | 279 ++++++++++++++++++++++
9 files changed, 537 insertions(+)
create mode 100644 libc/src/stdio/fmemopen.cpp
create mode 100644 libc/src/stdio/fmemopen.h
create mode 100644 libc/test/src/stdio/fmemopen_test.cpp
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 03053ac04f743..7a894683fdfea 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1236,6 +1236,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.stdio.fgets
libc.src.stdio.fileno
libc.src.stdio.flockfile
+ libc.src.stdio.fmemopen
libc.src.stdio.fopen
libc.src.stdio.freopen
libc.src.stdio.fopencookie
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 5db369a9d871d..c74521ff22995 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1423,6 +1423,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.stdio.fgets
libc.src.stdio.fileno
libc.src.stdio.flockfile
+ libc.src.stdio.fmemopen
libc.src.stdio.fopen
libc.src.stdio.freopen
libc.src.stdio.fopencookie
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 3cbbb7b3e9f79..3a5cb0a822473 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1436,6 +1436,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.stdio.fgets
libc.src.stdio.fileno
libc.src.stdio.flockfile
+ libc.src.stdio.fmemopen
libc.src.stdio.fopen
libc.src.stdio.freopen
libc.src.stdio.fopencookie
diff --git a/libc/include/stdio.yaml b/libc/include/stdio.yaml
index ef725dd4fe619..bb28dadd29b3b 100644
--- a/libc/include/stdio.yaml
+++ b/libc/include/stdio.yaml
@@ -139,6 +139,14 @@ functions:
return_type: void
arguments:
- type: FILE *
+ - name: fmemopen
+ standards:
+ - posix
+ return_type: FILE *
+ arguments:
+ - type: void *__restrict
+ - type: size_t
+ - type: const char *__restrict
- name: fopen
standards:
- stdc
diff --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt
index 3ef6e4f7b7d01..b9af9cf319d5d 100644
--- a/libc/src/stdio/CMakeLists.txt
+++ b/libc/src/stdio/CMakeLists.txt
@@ -52,6 +52,28 @@ add_entrypoint_object(
libc.src.__support.File.platform_file
)
+add_entrypoint_object(
+ fmemopen
+ SRCS
+ fmemopen.cpp
+ HDRS
+ fmemopen.h
+ DEPENDS
+ libc.hdr.errno_macros
+ libc.hdr.stdint_proxy
+ libc.hdr.stdio_macros
+ libc.hdr.types.FILE
+ libc.hdr.types.off_t
+ libc.hdr.types.size_t
+ libc.src.__support.CPP.limits
+ libc.src.__support.CPP.new
+ libc.src.__support.File.file
+ libc.src.__support.alloc_checker
+ libc.src.__support.libc_errno
+ libc.src.__support.macros.null_check
+ libc.src.string.memory_utils.inline_memcpy
+)
+
add_entrypoint_object(
fopencookie
SRCS
diff --git a/libc/src/stdio/fmemopen.cpp b/libc/src/stdio/fmemopen.cpp
new file mode 100644
index 0000000000000..cccb6e5e3332e
--- /dev/null
+++ b/libc/src/stdio/fmemopen.cpp
@@ -0,0 +1,172 @@
+//===-- Implementation of fmemopen ----------------------------------------===//
+//
+// 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/stdio/fmemopen.h"
+
+#include "hdr/errno_macros.h"
+#include "hdr/stdint_proxy.h"
+#include "hdr/stdio_macros.h"
+#include "hdr/types/off_t.h"
+#include "src/__support/CPP/limits.h"
+#include "src/__support/CPP/new.h"
+#include "src/__support/File/file.h"
+#include "src/__support/alloc-checker.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
+#include "src/string/memory_utils/inline_memcpy.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace {
+
+class MemoryFile : public File {
+ // The stdio buffer is separate from the memory used as the file's contents.
+ // In particular, ungetc and setvbuf must not modify or free that memory.
+ uint8_t stream_buffer[DEFAULT_BUFFER_SIZE];
+ uint8_t *storage;
+ size_t capacity;
+ size_t position = 0;
+ size_t end = 0;
+ bool owns_storage;
+ bool append;
+
+ static FileIOResult memory_read(File *f, void *data, size_t size) {
+ auto *mf = static_cast<MemoryFile *>(f);
+ if (size == 0 || mf->position >= mf->end)
+ return 0;
+ size_t available = mf->end - mf->position;
+ size_t count = size < available ? size : available;
+ inline_memcpy(data, mf->storage + mf->position, count);
+ mf->position += count;
+ return count;
+ }
+
+ static FileIOResult memory_write(File *f, const void *data, size_t size) {
+ auto *mf = static_cast<MemoryFile *>(f);
+ if (size == 0)
+ return 0;
+ size_t start = mf->append ? mf->end : mf->position;
+ size_t available = mf->capacity - start;
+ size_t count = size < available ? size : available;
+ if (count == 0)
+ return {0, ENOSPC};
+ inline_memcpy(mf->storage + start, data, count);
+ mf->position = start + count;
+ if (mf->position > mf->end) {
+ mf->end = mf->position;
+ // The terminator is not part of the file contents. All capacity bytes
+ // may hold data, in which case there is no room for a terminator.
+ if (mf->end < mf->capacity)
+ mf->storage[mf->end] = '\0';
+ }
+ return {count, count < size ? ENOSPC : 0};
+ }
+
+ static ErrorOr<off_t> memory_seek(File *f, off_t offset, int whence) {
+ auto *mf = static_cast<MemoryFile *>(f);
+ size_t base;
+ switch (whence) {
+ case SEEK_SET:
+ base = 0;
+ break;
+ case SEEK_CUR:
+ base = mf->position;
+ break;
+ case SEEK_END:
+ base = mf->end;
+ break;
+ default:
+ return Error(EINVAL);
+ }
+
+ size_t next;
+ if (offset < 0) {
+ // Avoid negating the minimum off_t, and compare before narrowing to
+ // size_t (which may be smaller than off_t).
+ uintmax_t distance = static_cast<uintmax_t>(-(offset + 1)) + 1;
+ if (distance > base)
+ return Error(EINVAL);
+ next = base - static_cast<size_t>(distance);
+ } else {
+ uintmax_t distance = static_cast<uintmax_t>(offset);
+ if (distance > mf->capacity - base)
+ return Error(EINVAL);
+ next = base + static_cast<size_t>(distance);
+ }
+ if (next > static_cast<uintmax_t>(cpp::numeric_limits<off_t>::max()))
+ return Error(EOVERFLOW);
+ mf->position = next;
+ return static_cast<off_t>(next);
+ }
+
+ static int memory_close(File *f) {
+ auto *mf = static_cast<MemoryFile *>(f);
+ File::remove_file(mf);
+ if (mf->owns_storage)
+ delete[] mf->storage;
+ delete mf;
+ return 0;
+ }
+
+public:
+ MemoryFile(uint8_t *storage, size_t capacity, bool owns_storage,
+ ModeFlags mode)
+ : File(&memory_write, &memory_read, &memory_seek, &memory_close,
+ stream_buffer, sizeof(stream_buffer), _IOFBF, false, mode),
+ storage(storage), capacity(capacity), owns_storage(owns_storage),
+ append(mode & static_cast<ModeFlags>(OpenMode::APPEND)) {
+ if (mode & static_cast<ModeFlags>(OpenMode::READ)) {
+ end = capacity;
+ } else if (mode & static_cast<ModeFlags>(OpenMode::WRITE)) {
+ if (capacity != 0)
+ storage[0] = '\0';
+ } else if (!owns_storage) {
+ while (end < capacity && storage[end] != '\0')
+ ++end;
+ position = end;
+ }
+ }
+};
+
+} // namespace
+
+LLVM_LIBC_FUNCTION(::FILE *, fmemopen,
+ (void *__restrict buf, size_t max_size,
+ const char *__restrict mode)) {
+ LIBC_CRASH_ON_NULLPTR(mode);
+ // Use the same mode parser as fopen. Binary mode has no special effect.
+ auto flags = File::mode_flags(mode);
+ if (flags == 0) {
+ libc_errno = EINVAL;
+ return nullptr;
+ }
+
+ auto *storage = static_cast<uint8_t *>(buf);
+ bool owns_storage = buf == nullptr;
+ if (owns_storage && max_size != 0) {
+ AllocChecker ac;
+ storage = new (ac) uint8_t[max_size];
+ if (!ac) {
+ libc_errno = ENOMEM;
+ return nullptr;
+ }
+ }
+
+ AllocChecker ac;
+ auto *file = new (ac) MemoryFile(storage, max_size, owns_storage, flags);
+ if (!ac) {
+ if (owns_storage)
+ delete[] storage;
+ libc_errno = ENOMEM;
+ return nullptr;
+ }
+ File::add_file(file);
+ return reinterpret_cast<::FILE *>(file);
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/fmemopen.h b/libc/src/stdio/fmemopen.h
new file mode 100644
index 0000000000000..efc25f7cb93b3
--- /dev/null
+++ b/libc/src/stdio/fmemopen.h
@@ -0,0 +1,23 @@
+//===-- Implementation header of fmemopen ----------------------*- 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_FMEMOPEN_H
+#define LLVM_LIBC_SRC_STDIO_FMEMOPEN_H
+
+#include "hdr/types/FILE.h"
+#include "hdr/types/size_t.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+::FILE *fmemopen(void *__restrict buf, size_t max_size,
+ const char *__restrict mode);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDIO_FMEMOPEN_H
diff --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt
index b25219ef3ca03..36692a9145b8d 100644
--- a/libc/test/src/stdio/CMakeLists.txt
+++ b/libc/test/src/stdio/CMakeLists.txt
@@ -142,6 +142,36 @@ add_libc_test(
libc.test.UnitTest.ErrnoCheckingTest
)
+add_libc_test(
+ fmemopen_test
+ SUITE
+ libc_stdio_unittests
+ SRCS
+ fmemopen_test.cpp
+ DEPENDS
+ libc.include.stdio
+ libc.hdr.stdio_macros
+ libc.hdr.types.off_t
+ libc.src.__support.CPP.limits
+ libc.src.__support.CPP.scope
+ libc.src.errno.errno
+ libc.src.stdio.clearerr
+ libc.src.stdio.fclose
+ libc.src.stdio.feof
+ libc.src.stdio.ferror
+ libc.src.stdio.fflush
+ libc.src.stdio.fgetc
+ libc.src.stdio.fmemopen
+ libc.src.stdio.fread
+ libc.src.stdio.fseek
+ libc.src.stdio.fseeko
+ libc.src.stdio.ftell
+ libc.src.stdio.fwrite
+ libc.src.stdio.setvbuf
+ libc.test.UnitTest.ErrnoCheckingTest
+ libc.test.UnitTest.MemoryMatcher
+)
+
add_libc_test(
fopencookie_test
SUITE
diff --git a/libc/test/src/stdio/fmemopen_test.cpp b/libc/test/src/stdio/fmemopen_test.cpp
new file mode 100644
index 0000000000000..cd8e8531fe1c4
--- /dev/null
+++ b/libc/test/src/stdio/fmemopen_test.cpp
@@ -0,0 +1,279 @@
+//===-- Unittests for the fmemopen function -------------------------------===//
+//
+// 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/stdio_macros.h"
+#include "hdr/types/off_t.h"
+#include "src/__support/CPP/limits.h"
+#include "src/__support/CPP/scope.h"
+#include "src/stdio/clearerr.h"
+#include "src/stdio/fclose.h"
+#include "src/stdio/feof.h"
+#include "src/stdio/ferror.h"
+#include "src/stdio/fflush.h"
+#include "src/stdio/fgetc.h"
+#include "src/stdio/fmemopen.h"
+#include "src/stdio/fread.h"
+#include "src/stdio/fseek.h"
+#include "src/stdio/fseeko.h"
+#include "src/stdio/ftell.h"
+#include "src/stdio/fwrite.h"
+#include "src/stdio/setvbuf.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/MemoryMatcher.h"
+#include "test/UnitTest/Test.h"
+
+using LlvmLibcFMemOpenTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+using MemoryView = LIBC_NAMESPACE::testing::MemoryView;
+using LIBC_NAMESPACE::cpp::scope_exit;
+
+TEST_F(LlvmLibcFMemOpenTest, InitialPositionAndEnd) {
+ struct Mode {
+ const char *name;
+ long position;
+ long end;
+ };
+ const Mode modes[] = {
+ {"r", 0, 8}, {"rb", 0, 8}, {"r+", 0, 8}, {"rb+", 0, 8}, {"r+b", 0, 8},
+ {"w", 0, 0}, {"wb", 0, 0}, {"w+", 0, 0}, {"wb+", 0, 0}, {"w+b", 0, 0},
+ {"a", 2, 2}, {"ab", 2, 2}, {"a+", 2, 2}, {"ab+", 2, 2}, {"a+b", 2, 2},
+ };
+ for (const auto &mode : modes) {
+ char storage[] = {'A', 'B', '\0', 'x', 'x', 'x', 'x', 'x'};
+ ::FILE *f = LIBC_NAMESPACE::fmemopen(storage, sizeof(storage), mode.name);
+ ASSERT_TRUE(f != nullptr);
+ scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); });
+ EXPECT_EQ(mode.position, LIBC_NAMESPACE::ftell(f));
+ EXPECT_EQ(mode.name[0] == 'w' ? '\0' : 'A', storage[0]);
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_END));
+ EXPECT_EQ(mode.end, LIBC_NAMESPACE::ftell(f));
+ }
+}
+
+TEST_F(LlvmLibcFMemOpenTest, ReadIncludesNullAndStopsAtEnd) {
+ char storage[] = {'A', '\0', 'B', 'C'};
+ char output[] = {'?', '?', '?', '?', '?', '?'};
+ ::FILE *f = LIBC_NAMESPACE::fmemopen(storage, sizeof(storage), "r");
+ ASSERT_TRUE(f != nullptr);
+ scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); });
+ EXPECT_EQ(size_t(4), LIBC_NAMESPACE::fread(output, 1, sizeof(output), f));
+ const char expected[] = {'A', '\0', 'B', 'C', '?', '?'};
+ EXPECT_MEM_EQ(MemoryView(expected, sizeof(expected)),
+ MemoryView(output, sizeof(output)));
+ EXPECT_NE(0, LIBC_NAMESPACE::feof(f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::ferror(f));
+ EXPECT_EQ(size_t(0), LIBC_NAMESPACE::fread(output, 1, 1, f));
+ EXPECT_EQ(4L, LIBC_NAMESPACE::ftell(f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_SET));
+ EXPECT_EQ(0, LIBC_NAMESPACE::feof(f));
+ EXPECT_EQ(int('A'), LIBC_NAMESPACE::fgetc(f));
+}
+
+TEST_F(LlvmLibcFMemOpenTest, WriteAndOverwrite) {
+ char storage[8];
+ ::FILE *f = LIBC_NAMESPACE::fmemopen(storage, sizeof(storage), "w+");
+ ASSERT_TRUE(f != nullptr);
+ scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); });
+ EXPECT_EQ(size_t(5), LIBC_NAMESPACE::fwrite("ABCDE", 1, 5, f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fflush(f));
+ EXPECT_STREQ("ABCDE", storage);
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 1, SEEK_SET));
+ EXPECT_EQ(size_t(1), LIBC_NAMESPACE::fwrite("X", 1, 1, f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fflush(f));
+ EXPECT_STREQ("AXCDE", storage);
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_END));
+ EXPECT_EQ(5L, LIBC_NAMESPACE::ftell(f));
+ EXPECT_EQ(size_t(1), LIBC_NAMESPACE::fwrite("F", 1, 1, f));
+ // Seeking flushes output and allows an update stream to switch to reading.
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_SET));
+ char output[8];
+ EXPECT_EQ(size_t(6), LIBC_NAMESPACE::fread(output, 1, sizeof(output), f));
+ EXPECT_MEM_EQ(MemoryView("AXCDEF", 6), MemoryView(output, 6));
+}
+
+TEST_F(LlvmLibcFMemOpenTest, ExactCapacityAndClose) {
+ const int buffer_modes[] = {_IONBF, _IOFBF, _IOLBF};
+ for (int buffering : buffer_modes) {
+ char guarded[] = {'L', '?', '?', '?', '?', 'R'};
+ ::FILE *f = LIBC_NAMESPACE::fmemopen(guarded + 1, 4, "w");
+ ASSERT_TRUE(f != nullptr);
+ EXPECT_EQ(0, LIBC_NAMESPACE::setvbuf(f, nullptr, buffering, 16));
+ EXPECT_EQ(size_t(4), LIBC_NAMESPACE::fwrite("ABCD", 1, 4, f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f));
+ const char expected[] = {'L', 'A', 'B', 'C', 'D', 'R'};
+ EXPECT_MEM_EQ(MemoryView(expected, sizeof(expected)),
+ MemoryView(guarded, sizeof(guarded)));
+ }
+}
+
+TEST_F(LlvmLibcFMemOpenTest, CloseFlushesPendingOutput) {
+ char storage[8];
+ ::FILE *f = LIBC_NAMESPACE::fmemopen(storage, sizeof(storage), "w");
+ ASSERT_TRUE(f != nullptr);
+ EXPECT_EQ(size_t(3), LIBC_NAMESPACE::fwrite("ABC", 1, 3, f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f));
+ EXPECT_STREQ("ABC", storage);
+}
+
+TEST_F(LlvmLibcFMemOpenTest, AppendAfterSeekAndRead) {
+ const int buffer_modes[] = {_IONBF, _IOFBF};
+ for (int buffering : buffer_modes) {
+ char storage[8] = "AB";
+ ::FILE *f = LIBC_NAMESPACE::fmemopen(storage, sizeof(storage), "a+");
+ ASSERT_TRUE(f != nullptr);
+ scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); });
+ ASSERT_EQ(0, LIBC_NAMESPACE::setvbuf(f, nullptr, buffering, 16));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_SET));
+ EXPECT_EQ(int('A'), LIBC_NAMESPACE::fgetc(f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_CUR));
+ EXPECT_EQ(size_t(1), LIBC_NAMESPACE::fwrite("X", 1, 1, f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fflush(f));
+ EXPECT_EQ(3L, LIBC_NAMESPACE::ftell(f));
+ EXPECT_STREQ("ABX", storage);
+ // Even a position beyond the current end must not create an append hole.
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 7, SEEK_SET));
+ EXPECT_EQ(size_t(1), LIBC_NAMESPACE::fwrite("Y", 1, 1, f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fflush(f));
+ EXPECT_STREQ("ABXY", storage);
+ }
+}
+
+TEST_F(LlvmLibcFMemOpenTest, AppendTracksEndAcrossNullBytes) {
+ char storage[8] = "";
+ ::FILE *f = LIBC_NAMESPACE::fmemopen(storage, sizeof(storage), "a+");
+ ASSERT_TRUE(f != nullptr);
+ scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); });
+ EXPECT_EQ(0L, LIBC_NAMESPACE::ftell(f));
+ const char first[] = {'A', '\0', 'B'};
+ EXPECT_EQ(sizeof(first), LIBC_NAMESPACE::fwrite(first, 1, sizeof(first), f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fflush(f));
+ EXPECT_EQ(size_t(1), LIBC_NAMESPACE::fwrite("C", 1, 1, f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fflush(f));
+ const char expected[] = {'A', '\0', 'B', 'C', '\0'};
+ EXPECT_MEM_EQ(MemoryView(expected, sizeof(expected)),
+ MemoryView(storage, sizeof(expected)));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_END));
+ EXPECT_EQ(4L, LIBC_NAMESPACE::ftell(f));
+}
+
+TEST_F(LlvmLibcFMemOpenTest, SeekBoundsAndEnd) {
+ char storage[8];
+ ::FILE *f = LIBC_NAMESPACE::fmemopen(storage, sizeof(storage), "w+");
+ ASSERT_TRUE(f != nullptr);
+ scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); });
+ EXPECT_EQ(size_t(3), LIBC_NAMESPACE::fwrite("ABC", 1, 3, f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 8, SEEK_SET));
+ EXPECT_EQ(8L, LIBC_NAMESPACE::ftell(f));
+ EXPECT_NE(0, LIBC_NAMESPACE::fseek(f, 9, SEEK_SET));
+ ASSERT_ERRNO_EQ(EINVAL);
+ EXPECT_EQ(8L, LIBC_NAMESPACE::ftell(f));
+ EXPECT_NE(0, LIBC_NAMESPACE::fseek(f, -1, SEEK_SET));
+ ASSERT_ERRNO_EQ(EINVAL);
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_END));
+ EXPECT_EQ(3L, LIBC_NAMESPACE::ftell(f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 2, SEEK_END));
+ EXPECT_EQ(5L, LIBC_NAMESPACE::ftell(f));
+ // Reading past the data end must not advance the memory stream's position.
+ EXPECT_EQ(EOF, LIBC_NAMESPACE::fgetc(f));
+ EXPECT_NE(0, LIBC_NAMESPACE::feof(f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::ferror(f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_CUR));
+ EXPECT_EQ(5L, LIBC_NAMESPACE::ftell(f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, -1, SEEK_END));
+ EXPECT_EQ(2L, LIBC_NAMESPACE::ftell(f));
+ const off_t offsets[] = {LIBC_NAMESPACE::cpp::numeric_limits<off_t>::min(),
+ LIBC_NAMESPACE::cpp::numeric_limits<off_t>::max()};
+ for (off_t offset : offsets) {
+ EXPECT_NE(0, LIBC_NAMESPACE::fseeko(f, offset, SEEK_CUR));
+ ASSERT_ERRNO_EQ(EINVAL);
+ EXPECT_EQ(2L, LIBC_NAMESPACE::ftell(f));
+ }
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 6, SEEK_SET));
+ EXPECT_EQ(size_t(1), LIBC_NAMESPACE::fwrite("Z", 1, 1, f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_END));
+ EXPECT_EQ(7L, LIBC_NAMESPACE::ftell(f));
+ EXPECT_EQ('Z', storage[6]);
+ EXPECT_EQ('\0', storage[7]);
+ EXPECT_NE(0, LIBC_NAMESPACE::fseek(f, 0, -1));
+ ASSERT_ERRNO_EQ(EINVAL);
+ // The contents of the gap between the old end and position 6 are unspecified.
+}
+
+TEST_F(LlvmLibcFMemOpenTest, InternalBuffer) {
+ const char *update_modes[] = {"w+", "a+", "r+"};
+ for (const char *mode : update_modes) {
+ ::FILE *f = LIBC_NAMESPACE::fmemopen(nullptr, 16, mode);
+ ASSERT_TRUE(f != nullptr);
+ scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); });
+ EXPECT_EQ(0L, LIBC_NAMESPACE::ftell(f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_END));
+ EXPECT_EQ(mode[0] == 'r' ? 16L : 0L, LIBC_NAMESPACE::ftell(f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_SET));
+ EXPECT_EQ(size_t(3), LIBC_NAMESPACE::fwrite("ABC", 1, 3, f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_SET));
+ char output[3];
+ EXPECT_EQ(sizeof(output),
+ LIBC_NAMESPACE::fread(output, 1, sizeof(output), f));
+ EXPECT_MEM_EQ(MemoryView("ABC", 3), MemoryView(output, 3));
+ }
+ // Non-update modes are accepted, just as with an externally supplied buffer.
+ const char *other_modes[] = {"r", "w", "a"};
+ for (const char *mode : other_modes) {
+ ::FILE *f = LIBC_NAMESPACE::fmemopen(nullptr, 4, mode);
+ ASSERT_TRUE(f != nullptr);
+ EXPECT_EQ(0L, LIBC_NAMESPACE::ftell(f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f));
+ }
+}
+
+TEST_F(LlvmLibcFMemOpenTest, ZeroCapacity) {
+ char guard = 'X';
+ void *buffers[] = {&guard, nullptr};
+ for (void *buf : buffers) {
+ const char *modes[] = {"r+", "w+", "a+"};
+ for (const char *mode : modes) {
+ ::FILE *f = LIBC_NAMESPACE::fmemopen(buf, 0, mode);
+ ASSERT_TRUE(f != nullptr);
+ scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); });
+ ASSERT_EQ(0, LIBC_NAMESPACE::setvbuf(f, nullptr, _IOFBF, 1));
+ EXPECT_EQ(0L, LIBC_NAMESPACE::ftell(f));
+ EXPECT_EQ(EOF, LIBC_NAMESPACE::fgetc(f));
+ EXPECT_NE(0, LIBC_NAMESPACE::feof(f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_SET));
+ EXPECT_EQ(size_t(0), LIBC_NAMESPACE::fwrite("ABC", 1, 3, f));
+ ASSERT_ERRNO_EQ(ENOSPC);
+ EXPECT_NE(0, LIBC_NAMESPACE::ferror(f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_END));
+ EXPECT_EQ(0L, LIBC_NAMESPACE::ftell(f));
+ EXPECT_NE(0, LIBC_NAMESPACE::fseek(f, 1, SEEK_SET));
+ ASSERT_ERRNO_EQ(EINVAL);
+ EXPECT_EQ('X', guard);
+ }
+ }
+}
+
+TEST_F(LlvmLibcFMemOpenTest, ShortWriteAndRecovery) {
+ char guarded[] = {'L', '?', '?', '?', '?', 'R'};
+ ::FILE *f = LIBC_NAMESPACE::fmemopen(guarded + 1, 4, "w+");
+ ASSERT_TRUE(f != nullptr);
+ scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); });
+ ASSERT_EQ(0, LIBC_NAMESPACE::setvbuf(f, nullptr, _IOFBF, 1));
+ EXPECT_EQ(size_t(4), LIBC_NAMESPACE::fwrite("ABCDEF", 1, 6, f));
+ ASSERT_ERRNO_EQ(ENOSPC);
+ EXPECT_NE(0, LIBC_NAMESPACE::ferror(f));
+ EXPECT_EQ(4L, LIBC_NAMESPACE::ftell(f));
+ EXPECT_EQ(size_t(0), LIBC_NAMESPACE::fwrite("XYZ", 1, 3, f));
+ ASSERT_ERRNO_EQ(ENOSPC);
+ LIBC_NAMESPACE::clearerr(f);
+ EXPECT_EQ(0, LIBC_NAMESPACE::ferror(f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_SET));
+ EXPECT_EQ(size_t(1), LIBC_NAMESPACE::fwrite("X", 1, 1, f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fflush(f));
+ const char expected[] = {'L', 'X', 'B', 'C', 'D', 'R'};
+ EXPECT_MEM_EQ(MemoryView(expected, sizeof(expected)),
+ MemoryView(guarded, sizeof(guarded)));
+}
>From 46cdedb0f653232911c61629f01e8293c21336f3 Mon Sep 17 00:00:00 2001
From: Jihyeon Jeong <jh.jeong129 at gmail.com>
Date: Wed, 9 Sep 2026 17:47:46 +0000
Subject: [PATCH 2/3] [libc] Address fmemopen review comments
---
libc/src/stdio/CMakeLists.txt | 2 +-
libc/src/stdio/fmemopen.cpp | 45 ++++++++++++-------------
libc/src/stdio/fmemopen.h | 7 +++-
libc/test/src/stdio/CMakeLists.txt | 1 +
libc/test/src/stdio/fmemopen_test.cpp | 48 ++++++++++++++++++---------
5 files changed, 61 insertions(+), 42 deletions(-)
diff --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt
index b9af9cf319d5d..a0d4e86674b95 100644
--- a/libc/src/stdio/CMakeLists.txt
+++ b/libc/src/stdio/CMakeLists.txt
@@ -65,7 +65,7 @@ add_entrypoint_object(
libc.hdr.types.FILE
libc.hdr.types.off_t
libc.hdr.types.size_t
- libc.src.__support.CPP.limits
+ libc.src.__support.CPP.algorithm
libc.src.__support.CPP.new
libc.src.__support.File.file
libc.src.__support.alloc_checker
diff --git a/libc/src/stdio/fmemopen.cpp b/libc/src/stdio/fmemopen.cpp
index cccb6e5e3332e..04fbc16adcfb7 100644
--- a/libc/src/stdio/fmemopen.cpp
+++ b/libc/src/stdio/fmemopen.cpp
@@ -1,10 +1,15 @@
-//===-- Implementation of fmemopen ----------------------------------------===//
+//===----------------------------------------------------------------------===//
//
// 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
+/// Implementation of fmemopen, a POSIX function.
+///
+//===----------------------------------------------------------------------===//
#include "src/stdio/fmemopen.h"
@@ -12,7 +17,7 @@
#include "hdr/stdint_proxy.h"
#include "hdr/stdio_macros.h"
#include "hdr/types/off_t.h"
-#include "src/__support/CPP/limits.h"
+#include "src/__support/CPP/algorithm.h"
#include "src/__support/CPP/new.h"
#include "src/__support/File/file.h"
#include "src/__support/alloc-checker.h"
@@ -36,23 +41,24 @@ class MemoryFile : public File {
bool append;
static FileIOResult memory_read(File *f, void *data, size_t size) {
- auto *mf = static_cast<MemoryFile *>(f);
+ auto *mf = reinterpret_cast<MemoryFile *>(f);
if (size == 0 || mf->position >= mf->end)
return 0;
size_t available = mf->end - mf->position;
- size_t count = size < available ? size : available;
+ size_t count = cpp::min(size, available);
inline_memcpy(data, mf->storage + mf->position, count);
mf->position += count;
return count;
}
static FileIOResult memory_write(File *f, const void *data, size_t size) {
- auto *mf = static_cast<MemoryFile *>(f);
+ auto *mf = reinterpret_cast<MemoryFile *>(f);
if (size == 0)
return 0;
size_t start = mf->append ? mf->end : mf->position;
size_t available = mf->capacity - start;
- size_t count = size < available ? size : available;
+ size_t count = cpp::min(size, available);
+ // POSIX fflush and fseek specify ENOSPC for a full fmemopen buffer.
if (count == 0)
return {0, ENOSPC};
inline_memcpy(mf->storage + start, data, count);
@@ -68,7 +74,7 @@ class MemoryFile : public File {
}
static ErrorOr<off_t> memory_seek(File *f, off_t offset, int whence) {
- auto *mf = static_cast<MemoryFile *>(f);
+ auto *mf = reinterpret_cast<MemoryFile *>(f);
size_t base;
switch (whence) {
case SEEK_SET:
@@ -84,28 +90,19 @@ class MemoryFile : public File {
return Error(EINVAL);
}
- size_t next;
- if (offset < 0) {
- // Avoid negating the minimum off_t, and compare before narrowing to
- // size_t (which may be smaller than off_t).
- uintmax_t distance = static_cast<uintmax_t>(-(offset + 1)) + 1;
- if (distance > base)
- return Error(EINVAL);
- next = base - static_cast<size_t>(distance);
- } else {
- uintmax_t distance = static_cast<uintmax_t>(offset);
- if (distance > mf->capacity - base)
- return Error(EINVAL);
- next = base + static_cast<size_t>(distance);
- }
- if (next > static_cast<uintmax_t>(cpp::numeric_limits<off_t>::max()))
- return Error(EOVERFLOW);
+ // The bounds fit in off_t since no object is larger than PTRDIFF_MAX, so
+ // comparing in off_t needs no negation of offset or narrowing to size_t.
+ off_t min_offset = -static_cast<off_t>(base);
+ off_t max_offset = static_cast<off_t>(mf->capacity - base);
+ if (offset < min_offset || offset > max_offset)
+ return Error(EINVAL);
+ size_t next = base + static_cast<size_t>(offset);
mf->position = next;
return static_cast<off_t>(next);
}
static int memory_close(File *f) {
- auto *mf = static_cast<MemoryFile *>(f);
+ auto *mf = reinterpret_cast<MemoryFile *>(f);
File::remove_file(mf);
if (mf->owns_storage)
delete[] mf->storage;
diff --git a/libc/src/stdio/fmemopen.h b/libc/src/stdio/fmemopen.h
index efc25f7cb93b3..5fe2ad4536743 100644
--- a/libc/src/stdio/fmemopen.h
+++ b/libc/src/stdio/fmemopen.h
@@ -1,10 +1,15 @@
-//===-- Implementation header of fmemopen ----------------------*- 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
//
//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Declaration of fmemopen a POSIX function.
+///
+//===----------------------------------------------------------------------===//
#ifndef LLVM_LIBC_SRC_STDIO_FMEMOPEN_H
#define LLVM_LIBC_SRC_STDIO_FMEMOPEN_H
diff --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt
index 36692a9145b8d..8e1e9ff056287 100644
--- a/libc/test/src/stdio/CMakeLists.txt
+++ b/libc/test/src/stdio/CMakeLists.txt
@@ -169,6 +169,7 @@ add_libc_test(
libc.src.stdio.fwrite
libc.src.stdio.setvbuf
libc.test.UnitTest.ErrnoCheckingTest
+ libc.test.UnitTest.ErrnoSetterMatcher
libc.test.UnitTest.MemoryMatcher
)
diff --git a/libc/test/src/stdio/fmemopen_test.cpp b/libc/test/src/stdio/fmemopen_test.cpp
index cd8e8531fe1c4..1736580914a45 100644
--- a/libc/test/src/stdio/fmemopen_test.cpp
+++ b/libc/test/src/stdio/fmemopen_test.cpp
@@ -1,10 +1,15 @@
-//===-- Unittests for the fmemopen function -------------------------------===//
+//===----------------------------------------------------------------------===//
//
// 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
+/// Unit tests for fmemopen.
+///
+//===----------------------------------------------------------------------===//
#include "hdr/stdio_macros.h"
#include "hdr/types/off_t.h"
@@ -24,12 +29,15 @@
#include "src/stdio/fwrite.h"
#include "src/stdio/setvbuf.h"
#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/ErrnoSetterMatcher.h"
#include "test/UnitTest/MemoryMatcher.h"
#include "test/UnitTest/Test.h"
using LlvmLibcFMemOpenTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
using MemoryView = LIBC_NAMESPACE::testing::MemoryView;
using LIBC_NAMESPACE::cpp::scope_exit;
+using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
+using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
TEST_F(LlvmLibcFMemOpenTest, InitialPositionAndEnd) {
struct Mode {
@@ -165,41 +173,49 @@ TEST_F(LlvmLibcFMemOpenTest, SeekBoundsAndEnd) {
::FILE *f = LIBC_NAMESPACE::fmemopen(storage, sizeof(storage), "w+");
ASSERT_TRUE(f != nullptr);
scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); });
+
EXPECT_EQ(size_t(3), LIBC_NAMESPACE::fwrite("ABC", 1, 3, f));
- EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 8, SEEK_SET));
+ ASSERT_THAT(LIBC_NAMESPACE::fseek(f, 8, SEEK_SET), Succeeds());
EXPECT_EQ(8L, LIBC_NAMESPACE::ftell(f));
- EXPECT_NE(0, LIBC_NAMESPACE::fseek(f, 9, SEEK_SET));
- ASSERT_ERRNO_EQ(EINVAL);
+
+ ASSERT_THAT(LIBC_NAMESPACE::fseek(f, 9, SEEK_SET), Fails(EINVAL));
EXPECT_EQ(8L, LIBC_NAMESPACE::ftell(f));
- EXPECT_NE(0, LIBC_NAMESPACE::fseek(f, -1, SEEK_SET));
- ASSERT_ERRNO_EQ(EINVAL);
- EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_END));
+
+ ASSERT_THAT(LIBC_NAMESPACE::fseek(f, -1, SEEK_SET), Fails(EINVAL));
+
+ ASSERT_THAT(LIBC_NAMESPACE::fseek(f, 0, SEEK_END), Succeeds());
EXPECT_EQ(3L, LIBC_NAMESPACE::ftell(f));
- EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 2, SEEK_END));
+
+ ASSERT_THAT(LIBC_NAMESPACE::fseek(f, 2, SEEK_END), Succeeds());
EXPECT_EQ(5L, LIBC_NAMESPACE::ftell(f));
+
// Reading past the data end must not advance the memory stream's position.
EXPECT_EQ(EOF, LIBC_NAMESPACE::fgetc(f));
EXPECT_NE(0, LIBC_NAMESPACE::feof(f));
EXPECT_EQ(0, LIBC_NAMESPACE::ferror(f));
- EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_CUR));
+
+ ASSERT_THAT(LIBC_NAMESPACE::fseek(f, 0, SEEK_CUR), Succeeds());
EXPECT_EQ(5L, LIBC_NAMESPACE::ftell(f));
- EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, -1, SEEK_END));
+
+ ASSERT_THAT(LIBC_NAMESPACE::fseek(f, -1, SEEK_END), Succeeds());
EXPECT_EQ(2L, LIBC_NAMESPACE::ftell(f));
+
const off_t offsets[] = {LIBC_NAMESPACE::cpp::numeric_limits<off_t>::min(),
LIBC_NAMESPACE::cpp::numeric_limits<off_t>::max()};
for (off_t offset : offsets) {
- EXPECT_NE(0, LIBC_NAMESPACE::fseeko(f, offset, SEEK_CUR));
- ASSERT_ERRNO_EQ(EINVAL);
+ ASSERT_THAT(LIBC_NAMESPACE::fseeko(f, offset, SEEK_CUR), Fails(EINVAL));
EXPECT_EQ(2L, LIBC_NAMESPACE::ftell(f));
}
- EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 6, SEEK_SET));
+
+ ASSERT_THAT(LIBC_NAMESPACE::fseek(f, 6, SEEK_SET), Succeeds());
EXPECT_EQ(size_t(1), LIBC_NAMESPACE::fwrite("Z", 1, 1, f));
- EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_END));
+
+ ASSERT_THAT(LIBC_NAMESPACE::fseek(f, 0, SEEK_END), Succeeds());
EXPECT_EQ(7L, LIBC_NAMESPACE::ftell(f));
EXPECT_EQ('Z', storage[6]);
EXPECT_EQ('\0', storage[7]);
- EXPECT_NE(0, LIBC_NAMESPACE::fseek(f, 0, -1));
- ASSERT_ERRNO_EQ(EINVAL);
+
+ ASSERT_THAT(LIBC_NAMESPACE::fseek(f, 0, -1), Fails(EINVAL));
// The contents of the gap between the old end and position 6 are unspecified.
}
>From 8759fa9307d973fee6dbaecf718b2a7852e6619b Mon Sep 17 00:00:00 2001
From: Jihyeon Jeong <jh.jeong129 at gmail.com>
Date: Thu, 10 Sep 2026 01:00:51 +0000
Subject: [PATCH 3/3] [libc] Make fmemopen seek bounds const
---
libc/src/stdio/fmemopen.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/libc/src/stdio/fmemopen.cpp b/libc/src/stdio/fmemopen.cpp
index 04fbc16adcfb7..d88426452fd2a 100644
--- a/libc/src/stdio/fmemopen.cpp
+++ b/libc/src/stdio/fmemopen.cpp
@@ -92,8 +92,8 @@ class MemoryFile : public File {
// The bounds fit in off_t since no object is larger than PTRDIFF_MAX, so
// comparing in off_t needs no negation of offset or narrowing to size_t.
- off_t min_offset = -static_cast<off_t>(base);
- off_t max_offset = static_cast<off_t>(mf->capacity - base);
+ const off_t min_offset = -static_cast<off_t>(base);
+ const off_t max_offset = static_cast<off_t>(mf->capacity - base);
if (offset < min_offset || offset > max_offset)
return Error(EINVAL);
size_t next = base + static_cast<size_t>(offset);
More information about the libc-commits
mailing list