Allow subprocess API to inherit the current process `environ`.

This is to be used by the Centipede adaptor.

PiperOrigin-RevId: 763133244
diff --git a/fuzztest/internal/subprocess.cc b/fuzztest/internal/subprocess.cc
index a6951a8..a670bd7 100644
--- a/fuzztest/internal/subprocess.cc
+++ b/fuzztest/internal/subprocess.cc
@@ -29,6 +29,7 @@
 #endif  // !defined(_MSC_VER)
 
 #include <future>
+#include <optional>
 #include <string>
 #include <utility>
 #include <vector>
@@ -42,6 +43,10 @@
 #include "absl/types/span.h"
 #include "./fuzztest/internal/logging.h"
 
+// Needed to pass the current environment to posix_spawn, which needs an
+// explicit envp without an option to inherit implicitly.
+extern char** environ;
+
 namespace fuzztest::internal {
 
 #if !defined(_MSC_VER) && !(defined(__ANDROID_MIN_SDK_VERSION__) && \
@@ -66,7 +71,8 @@
       absl::Span<const std::string> command_line,
       absl::FunctionRef<void(absl::string_view)> on_stdout_output,
       absl::FunctionRef<void(absl::string_view)> on_stderr_output,
-      const absl::flat_hash_map<std::string, std::string>& environment,
+      const std::optional<absl::flat_hash_map<std::string, std::string>>&
+          environment,
       absl::Duration timeout);
 
  private:
@@ -77,13 +83,13 @@
   void StartWatchdog(absl::Duration timeout);
   pid_t StartChild(
       absl::Span<const std::string> command_line,
-      const absl::flat_hash_map<std::string, std::string>& environment);
+      const std::optional<absl::flat_hash_map<std::string, std::string>>&
+          environment);
   void ReadChildOutput(
       absl::FunctionRef<void(absl::string_view)> on_stdout_output,
       absl::FunctionRef<void(absl::string_view)> on_stderr_output);
 
-  // Pipe file descriptors pairs. Index 0 is for stdout, index 1 is for
-  // stderr.
+  // Pipe file descriptors pairs. Index 0 is for stdout, index 1 is for stderr.
   static constexpr int kStdOutIdx = 0;
   static constexpr int kStdErrIdx = 1;
   int parent_pipe_[2];
@@ -157,7 +163,8 @@
 // Do fork() and exec() in one step, using posix_spawnp().
 pid_t SubProcess::StartChild(
     absl::Span<const std::string> command_line,
-    const absl::flat_hash_map<std::string, std::string>& environment) {
+    const std::optional<absl::flat_hash_map<std::string, std::string>>&
+        environment) {
   posix_spawn_file_actions_t actions = CreateChildFileActions();
 
   // Create `argv` and `envp` parameters for exec().
@@ -168,18 +175,21 @@
   }
   argv[argc] = nullptr;
 
-  size_t envc = environment.size();
-  std::vector<char*> envp(envc + 1);
-  int i = 0;
-  for (const auto& [key, value] : environment) {
-    envp[i++] = strdup(absl::StrCat(key, "=", value).c_str());
+  std::vector<char*> envp;
+  if (environment.has_value()) {
+    size_t envc = environment->size();
+    envp.resize(envc + 1);
+    int i = 0;
+    for (const auto& [key, value] : *environment) {
+      envp[i++] = strdup(absl::StrCat(key, "=", value).c_str());
+    }
+    envp[envc] = nullptr;
   }
-  envp[envc] = nullptr;
 
   pid_t child_pid;
   int err;
   err = posix_spawnp(&child_pid, argv[0], &actions, nullptr, argv.data(),
-                     envp.data());
+                     environment.has_value() ? envp.data() : environ);
   FUZZTEST_INTERNAL_CHECK(err == 0,
                           "Cannot spawn child process: ", strerror(err));
 
@@ -212,6 +222,7 @@
 
   // Loop reading stdout and stderr from the child process.
   int fd_remain = fd_count;
+
   char buf[4096];
   while (fd_remain > 0) {
     int ret = poll(pfd, fd_count, -1);
@@ -294,7 +305,8 @@
     absl::Span<const std::string> command_line,
     absl::FunctionRef<void(absl::string_view)> on_stdout_output,
     absl::FunctionRef<void(absl::string_view)> on_stderr_output,
-    const absl::flat_hash_map<std::string, std::string>& environment,
+    const std::optional<absl::flat_hash_map<std::string, std::string>>&
+        environment,
     absl::Duration timeout) {
   CreatePipes();
   pid_t child_pid = StartChild(command_line, environment);
@@ -313,7 +325,8 @@
     absl::Span<const std::string> command_line,
     absl::FunctionRef<void(absl::string_view)> on_stdout_output,
     absl::FunctionRef<void(absl::string_view)> on_stderr_output,
-    const absl::flat_hash_map<std::string, std::string>& environment,
+    const std::optional<absl::flat_hash_map<std::string, std::string>>&
+        environment,
     absl::Duration timeout) {
 #if defined(_MSC_VER)
   FUZZTEST_INTERNAL_CHECK(false,
@@ -331,7 +344,8 @@
 
 RunResults RunCommand(
     absl::Span<const std::string> command_line,
-    const absl::flat_hash_map<std::string, std::string>& environment,
+    const std::optional<absl::flat_hash_map<std::string, std::string>>&
+        environment,
     absl::Duration timeout) {
   std::string stdout;
   std::string stderr;
diff --git a/fuzztest/internal/subprocess.h b/fuzztest/internal/subprocess.h
index 8d70020..142f308 100644
--- a/fuzztest/internal/subprocess.h
+++ b/fuzztest/internal/subprocess.h
@@ -16,6 +16,7 @@
 #define FUZZTEST_FUZZTEST_INTERNAL_SUBPROCESS_H_
 
 #include <iostream>
+#include <optional>
 #include <string>
 #include <variant>
 #include <vector>
@@ -98,23 +99,28 @@
 
 // Runs `command_line` in a subprocess and passes through its stdout/stderr to
 // `on_stdout_output` and `on_stderr_output` callbacks. Environment variables
-// can be set via `environment`. If optional `timeout` is provided, the process
-// is terminated after the given timeout. The timeout will be rounded up to
-// seconds.
+// can be set via `environment` if it is not std::nullopt, otherwise environment
+// will be inherited from the current process (`environ`). If optional `timeout`
+// is provided, the process is terminated after the given timeout. The timeout
+// will be rounded up to seconds.
 TerminationStatus RunCommandWithOutputCallbacks(
     absl::Span<const std::string> command_line,
     absl::FunctionRef<void(absl::string_view)> on_stdout_output,
     absl::FunctionRef<void(absl::string_view)> on_stderr_output,
-    const absl::flat_hash_map<std::string, std::string>& environment = {},
+    const std::optional<absl::flat_hash_map<std::string, std::string>>&
+        environment = {{}},
     absl::Duration timeout = absl::InfiniteDuration());
 
 // Runs `command_line` in a subprocess and returns the run results that captures
-// the stdout/stderr as strings. Environment variables can be set via
-// `environment`. If optional `timeout` is provided, the process is terminated
-// after the given timeout. The timeout will be rounded up to seconds.
+// the stdout/stderr as strings. Environment variables
+// can be set via `environment` if it is not std::nullopt, otherwise environment
+// will be inherited from the current process (`environ`). `environment`. If
+// optional `timeout` is provided, the process is terminated after the given
+// timeout. The timeout will be rounded up to seconds.
 RunResults RunCommand(
     absl::Span<const std::string> command_line,
-    const absl::flat_hash_map<std::string, std::string>& environment = {},
+    const std::optional<absl::flat_hash_map<std::string, std::string>>&
+        environment = {{}},
     absl::Duration timeout = absl::InfiniteDuration());
 
 }  // namespace fuzztest::internal
diff --git a/fuzztest/internal/subprocess_test.cc b/fuzztest/internal/subprocess_test.cc
index 844c0bc..f5d10bd 100644
--- a/fuzztest/internal/subprocess_test.cc
+++ b/fuzztest/internal/subprocess_test.cc
@@ -15,6 +15,7 @@
 #include "./fuzztest/internal/subprocess.h"
 
 #include <csignal>
+#include <optional>
 #include <sstream>
 #include <string>
 
@@ -27,6 +28,7 @@
 namespace {
 
 using ::testing::HasSubstr;
+using ::testing::Not;
 
 template <typename T>
 std::string ToString(T v) {
@@ -71,8 +73,27 @@
   EXPECT_DEATH(RunCommand({"not-a-binary"}), "Cannot spawn child process");
 }
 
+TEST(SubProcessTest, EnvironmentIsNotInheritedByDefault) {
+  setenv("THING", "1", /*overwrite=*/1);
+  auto [status, std_out, std_err] = RunCommand({"env"});
+  EXPECT_TRUE(status.Exited());
+  EXPECT_EQ(status, ExitCode(0));
+  EXPECT_THAT(std_out, Not(HasSubstr("THING=1")));
+  EXPECT_EQ(std_err, "");
+}
+
+TEST(SubProcessTest, EnvironmentIsInheritedIfRequested) {
+  setenv("THING", "1", /*overwrite=*/1);
+  auto [status, std_out, std_err] =
+      RunCommand({"env"}, /*environment=*/std::nullopt);
+  EXPECT_TRUE(status.Exited());
+  EXPECT_EQ(status, ExitCode(0));
+  EXPECT_THAT(std_out, HasSubstr("THING=1")) << std_out;
+  EXPECT_EQ(std_err, "");
+}
+
 TEST(SubProcessTest, PassedEnvironmentIsSet) {
-  auto [status, std_out, std_err] = RunCommand({"env"}, {{"THING", "42"}});
+  auto [status, std_out, std_err] = RunCommand({"env"}, {{{"THING", "42"}}});
   EXPECT_TRUE(status.Exited());
   EXPECT_EQ(status, ExitCode(0));
   EXPECT_EQ(std_out, "THING=42\n");