[libc-commits] [libc] 00cbe82 - [libc][stdlib] Avoid scanning values during environment lookup (#228633)

via libc-commits libc-commits at lists.llvm.org
Sat Oct 31 00:51:30 PDT 2026


Author: Chen Miao
Date: 2026-10-31T07:51:25Z
New Revision: 00cbe8231c064558a28b740ea2603301c3d649af

URL: https://github.com/llvm/llvm-project/commit/00cbe8231c064558a28b740ea2603301c3d649af
DIFF: https://github.com/llvm/llvm-project/commit/00cbe8231c064558a28b740ea2603301c3d649af.diff

LOG: [libc][stdlib] Avoid scanning values during environment lookup (#228633)

Constructing a string_view for each environment entry scans the entire
value to determine its length, even when the name does not match. This
makes lookups unnecessarily expensive for long values such as PATH.

Filter entries by their first character, then compare the remaining name
up to a mismatch or null terminator. Require a following '=' to match
the complete key, preserving support for empty names in putenv.

Closes #196016

Added: 
    

Modified: 
    libc/src/stdlib/CMakeLists.txt
    libc/src/stdlib/environ_internal.cpp
    libc/test/integration/src/stdlib/CMakeLists.txt
    libc/test/integration/src/stdlib/getenv_test.cpp

Removed: 
    


################################################################################
diff  --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index 7cdb1a5bbfdfb8..264a87ef35c624 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -98,6 +98,7 @@ set(environ_internal_deps
   libc.src.__support.macros.attributes
   libc.src.__support.macros.config
   libc.src.string.memory_utils.inline_memcpy
+  libc.src.string.memory_utils.inline_strcmp
 )
 set(environ_internal_compile_options ${environ_internal_gcc12_workaround})
 

diff  --git a/libc/src/stdlib/environ_internal.cpp b/libc/src/stdlib/environ_internal.cpp
index 7fc2fdafd48673..43159cada19042 100644
--- a/libc/src/stdlib/environ_internal.cpp
+++ b/libc/src/stdlib/environ_internal.cpp
@@ -18,6 +18,7 @@
 #include "src/__support/alloc-checker.h"
 #include "src/__support/macros/config.h"
 #include "src/string/memory_utils/inline_memcpy.h"
+#include "src/string/memory_utils/inline_strcmp.h"
 #ifdef LIBC_COPT_SUPPORT_ENVIRON
 #include "src/unistd/environ.h"
 #endif
@@ -83,10 +84,24 @@ cpp::optional<size_t> EnvironmentManager::find_var(cpp::string_view name) {
   if (!env_array)
     return cpp::nullopt;
 
-  for (size_t i = 0; i < count; i++) {
-    cpp::string_view current(env_array[i]);
-    if (current.starts_with(name) && current.size() > name.size() &&
-        current[name.size()] == '=')
+  auto comp = [](char l, char r) -> int {
+    return static_cast<unsigned char>(l) - static_cast<unsigned char>(r);
+  };
+
+  // Empty names, supported by putenv, match entries starting with '='.
+  const char first = name.empty() ? '=' : name.front();
+  if (first == '\0')
+    return cpp::nullopt;
+  const size_t start = name.empty() ? 0 : 1;
+
+  for (size_t i = 0; i < count; ++i) {
+    const char *entry = env_array[i];
+    if (entry[0] != first)
+      continue;
+
+    if (inline_strncmp(entry + start, name.data() + start, name.size() - start,
+                       comp) == 0 &&
+        entry[name.size()] == '=')
       return i;
   }
 

diff  --git a/libc/test/integration/src/stdlib/CMakeLists.txt b/libc/test/integration/src/stdlib/CMakeLists.txt
index 5eb405ce36aec4..6e0a90afafd320 100644
--- a/libc/test/integration/src/stdlib/CMakeLists.txt
+++ b/libc/test/integration/src/stdlib/CMakeLists.txt
@@ -11,6 +11,7 @@ add_integration_test(
   SRCS
     getenv_test.cpp
   DEPENDS
+    libc.src.stdlib.environ_internal
     libc.src.stdlib.getenv
   ENV
     FRANCE=Paris

diff  --git a/libc/test/integration/src/stdlib/getenv_test.cpp b/libc/test/integration/src/stdlib/getenv_test.cpp
index fe323c948afc48..d0218767ca8faf 100644
--- a/libc/test/integration/src/stdlib/getenv_test.cpp
+++ b/libc/test/integration/src/stdlib/getenv_test.cpp
@@ -6,6 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "src/stdlib/environ_internal.h"
 #include "src/stdlib/getenv.h"
 
 #include "test/IntegrationTest/test.h"
@@ -21,5 +22,36 @@ TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
   ASSERT_TRUE(LIBC_NAMESPACE::getenv("FRANC") == nullptr);
   ASSERT_TRUE(LIBC_NAMESPACE::getenv("FRANCE1") == nullptr);
 
+  auto &env = LIBC_NAMESPACE::internal::EnvironmentManager::get_instance();
+  // Remove inherited variables that could interfere with the lookup tests.
+  ASSERT_EQ(env.unset("LIBC_GETENV_TEST"), 0);
+  ASSERT_EQ(env.unset("LIBC_GETENV_TEST_LONG"), 0);
+
+  // Exercise entries shorter than the requested name, including entries
+  // without '='. Lookup must stop at the terminator of each entry.
+  char *const ORIGINAL = *env.begin();
+  static char empty_entry[] = "";
+  static char short_entry[] = "LIBC_GETENV_TEST";
+  static char long_key[] = "LIBC_GETENV_TEST_LONG=value";
+  static char empty_value[] = "LIBC_GETENV_TEST=";
+  static char value_with_equals[] = "LIBC_GETENV_TEST=first=second";
+
+  *env.begin() = empty_entry;
+  ASSERT_TRUE(LIBC_NAMESPACE::getenv("LIBC_GETENV_TEST") == nullptr);
+  *env.begin() = short_entry;
+  ASSERT_TRUE(LIBC_NAMESPACE::getenv("LIBC_GETENV_TEST") == nullptr);
+  ASSERT_TRUE(LIBC_NAMESPACE::getenv("LIBC_GETENV_TEST_LONG") == nullptr);
+
+  *env.begin() = long_key;
+  ASSERT_TRUE(LIBC_NAMESPACE::getenv("LIBC_GETENV_TEST") == nullptr);
+
+  *env.begin() = empty_value;
+  ASSERT_STREQ(LIBC_NAMESPACE::getenv("LIBC_GETENV_TEST"), "");
+
+  *env.begin() = value_with_equals;
+  ASSERT_STREQ(LIBC_NAMESPACE::getenv("LIBC_GETENV_TEST"), "first=second");
+
+  *env.begin() = ORIGINAL;
+
   return 0;
 }


        


More information about the libc-commits mailing list