[libc-commits] [libc] [libc] Add tmpnam implementation (PR #204901)
via libc-commits
libc-commits at lists.llvm.org
Sun Sep 6 00:28:00 PDT 2026
================
@@ -0,0 +1,142 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Tests for tmpnam
+/// See: https://pubs.opengroup.org/onlinepubs/9799919799/functions/tmpnam.html
+///
+//===----------------------------------------------------------------------===//
+#include "src/stdio/tmpnam.h"
+
+#include "hdr/stdio_macros.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/macros/config.h"
+#include "test/UnitTest/Test.h"
+
+#include "hdr/types/size_t.h"
+namespace {
+
+using LIBC_NAMESPACE::cpp::string_view;
+
+// The portable filename character set the implementation draws from, plus the
+// '/' that appears in the P_tmpdir prefix. Any byte in a returned name must be
+// one of these.
+constexpr char Allowed[] = "0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "_./";
+
+bool only_allowed_chars(string_view sv) {
+ for (char c : sv) {
+ if (!string_view(Allowed).contains(c))
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+// Caller-supplied buffer: the spec requires the return value to be exactly the
+// argument pointer, the string to be null-terminated within L_tmpnam bytes,
+// and the result to begin with the temp-dir prefix.
+TEST(LlvmLibcTmpnamTest, NonNullBufferReturnsSamePointer) {
+ char buf[L_tmpnam];
+ char *result = LIBC_NAMESPACE::tmpnam(buf);
+ ASSERT_EQ(result, buf);
+}
+
+TEST(LlvmLibcTmpnamTest, NonNullBufferIsNullTerminated) {
+ char buf[L_tmpnam];
+ char *result = LIBC_NAMESPACE::tmpnam(buf);
+ ASSERT_NE(result, static_cast<char *>(nullptr));
+ // A NULL must appear within the buffer bounds.
+ bool terminated = false;
+ for (size_t i = 0; i < L_tmpnam; ++i) {
+ if (result[i] == '\0') {
+ terminated = true;
+ break;
+ }
+ }
+ ASSERT_TRUE(terminated);
+}
+
+TEST(LlvmLibcTmpnamTest, ResultHasTempDirPrefix) {
+ char buf[L_tmpnam];
+ char *result = LIBC_NAMESPACE::tmpnam(buf);
+ ASSERT_NE(result, static_cast<char *>(nullptr));
+ string_view sv(result);
+ string_view prefix(P_tmpdir);
+ // P_tmpdir may not carry a trailing slash; the implementation always
+ // emits one separator, so check the directory portion is present at the head.
+ ASSERT_TRUE(sv.starts_with(prefix));
+}
+
+TEST(LlvmLibcTmpnamTest, ResultUsesOnlyPortableChars) {
+ char buf[L_tmpnam];
+ char *result = LIBC_NAMESPACE::tmpnam(buf);
+ ASSERT_NE(result, static_cast<char *>(nullptr));
+ ASSERT_TRUE(only_allowed_chars(string_view(result)));
+}
+
+// Null argument: the result lives in an internal static object; the returned
+// pointer must be non-null and carry the same structural guarantees.
+TEST(LlvmLibcTmpnamTest, NullBufferReturnsInternalObject) {
+ char *result = LIBC_NAMESPACE::tmpnam(nullptr);
+ ASSERT_NE(result, static_cast<char *>(nullptr));
+ string_view sv(result);
+ ASSERT_TRUE(sv.starts_with(string_view(P_tmpdir)));
+ ASSERT_TRUE(only_allowed_chars(sv));
+}
+
+// Check that the generated path length is within bounds: strictly less than
+// L_tmpnam and strictly greater than the temporary directory prefix.
+TEST(LlvmLibcTmpnamTest, ResultLengthWithinBound) {
+ char buf[L_tmpnam];
+ char *result = LIBC_NAMESPACE::tmpnam(buf);
+ ASSERT_NE(result, static_cast<char *>(nullptr));
+ string_view sv(result);
+ ASSERT_LT(sv.size(), static_cast<size_t>(L_tmpnam));
+ // Must be strictly longer than the prefix: a prefix with no random suffix
+ // would mean the generator produced an empty suffix.
+ ASSERT_GT(sv.size(), string_view(P_tmpdir).size());
+}
+
+// Successive calls should produce distinct strings.
+TEST(LlvmLibcTmpnamTest, SuccessiveCallsDiffer) {
+ char a[L_tmpnam];
+ char b[L_tmpnam];
+ char *ra = LIBC_NAMESPACE::tmpnam(a);
+ char *rb = LIBC_NAMESPACE::tmpnam(b);
+ ASSERT_NE(ra, static_cast<char *>(nullptr));
+ ASSERT_NE(rb, static_cast<char *>(nullptr));
+ ASSERT_FALSE(string_view(ra) == string_view(rb));
+}
+
+// Two calls with a null argument must return the SAME pointer (the address of
+// the single internal static object) The contents, however, are overwritten by
+// the second call.
+TEST(LlvmLibcTmpnamTest, NullCallsShareObjectButDifferInContent) {
+ char *first = LIBC_NAMESPACE::tmpnam(nullptr);
+ ASSERT_NE(first, static_cast<char *>(nullptr));
+
+ // Snapshot the first result before it is overwritten.
----------------
shubhe25p wrote:
Yes this was inefficient, I have updated it please review the latest commit.
https://github.com/llvm/llvm-project/pull/204901
More information about the libc-commits
mailing list