Skip to content

python: fix segfault on empty argv in main() #2003

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

senyai
Copy link
Contributor

@senyai senyai commented Jun 15, 2025

test:
python -c 'import google_benchmark as b;b.register(print);b.main([]);'

@senyai senyai force-pushed the senyai-fix-empty-args branch from d9d0138 to 82f5ae1 Compare June 15, 2025 17:18
@senyai senyai force-pushed the senyai-fix-empty-args branch from 82f5ae1 to 7fe6010 Compare June 15, 2025 18:07
@senyai
Copy link
Contributor Author

senyai commented Jun 15, 2025

I think there's more explicit and expressive way to update the logic:

if (!argv.empty()) {
  static std::string executable_name(argv[0]);
  ptrs[0] = const_cast<char*>(executable_name.c_str());
}

should I change it, @LebedevRI?

@senyai senyai force-pushed the senyai-fix-empty-args branch from 7fe6010 to f04717f Compare June 27, 2025 17:33
@senyai senyai force-pushed the senyai-fix-empty-args branch from f04717f to 91c8495 Compare June 27, 2025 17:34
@senyai
Copy link
Contributor Author

senyai commented Jun 27, 2025

Now I'm glad with this simple fix @LebedevRI

@senyai
Copy link
Contributor Author

senyai commented Jun 27, 2025

btw, my initial command for reproducing the issue is no longer giving me segmentation fault, it just says ValueError: argv cannot be an empty list, and must contain the program name as the first element. Not sure why, I didn't change anything. But this command python -c 'import google_benchmark as b;b.register(print);b.initialize([]);' should still give you a segfault in main.

Comment on lines +22 to +28
if (!ptrs.empty()) {
// The `argv` pointers here become invalid when this function returns, but
// benchmark holds the pointer to `argv[0]`. We create a static copy of it
// so it persists, and replace the pointer below.
static std::string executable_name(argv[0]);
ptrs[0] = const_cast<char*>(executable_name.c_str());
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this just exchange one bug for another?
The comment suggests that something uses the binary name (argv[0]) later on,
but now instead of "crashing" here, we'll crash the code that tries to take argv[0].

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code that tries to take argv[0] is in benchmark::Initialize, and there's already a check that ensures that it does not happen when argc is 0. That's why this little change is enough. I'm not sure how to make the comment better.

@@ -14,16 +14,18 @@ namespace {
namespace nb = nanobind;

std::vector<std::string> Initialize(const std::vector<std::string>& argv) {
// The `argv` pointers here become invalid when this function returns, but
Copy link
Collaborator

@LebedevRI LebedevRI Jun 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-std::vector<std::string> Initialize(const std::vector<std::string>& argv) {
-  // The `argv` pointers here become invalid when this function returns, but
-  // benchmark holds the pointer to `argv[0]`. We create a static copy of it
-  // so it persists, and replace the pointer below.
-  static std::string executable_name(argv[0]);
-  std::vector<char*> ptrs;
-  ptrs.reserve(argv.size());
-  for (auto& arg : argv) {
-    ptrs.push_back(const_cast<char*>(arg.c_str()));
-  }
-  ptrs[0] = const_cast<char*>(executable_name.c_str());
-  int argc = static_cast<int>(argv.size());
-  benchmark::Initialize(&argc, ptrs.data());
-  std::vector<std::string> remaining_argv;
-  remaining_argv.reserve(argc);
-  for (int i = 0; i < argc; ++i) {
-    remaining_argv.emplace_back(ptrs[i]);
-  }
-  return remaining_argv;
-}

+std::vector<std::string> Initialize(const std::vector<std::string>& argv) {
+  // The `argv` pointers here become invalid when this function returns, but
+  // benchmark holds the pointer to `argv[0]`. We create a static copy of it
+  // so it persists, and replace the pointer below.
+  std::vector<char*> ptrs;
+  if(argv.empty()) {
+    static std::string executable_name("unknown");
+    ptrs.emplace_back(executable_name.c_str());
+  } else {
+    ptrs.reserve(argv.size());
+    for (auto& arg : argv) {
+      ptrs.push_back(const_cast<char*>(arg.c_str()));
+    }
+    static std::string executable_name(argv[0]);
+    ptrs[0] = const_cast<char*>(executable_name.c_str());
+  }
+  int argc = static_cast<int>(ptrs.size());
+  benchmark::Initialize(&argc, ptrs.data());
+  std::vector<std::string> remaining_argv;
+  remaining_argv.reserve(argc);
+  for (int i = 0; i < argc; ++i) {
+    remaining_argv.emplace_back(ptrs[i]);
+  }
+  return remaining_argv;
+}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is okay to pass empty array to benchmark::Initialize

benchmark/src/benchmark.cc

Lines 753 to 754 in b20cea6

BenchmarkReporter::Context::executable_name =
((argc != nullptr) && *argc > 0) ? argv[0] : "unknown";

@LebedevRI
Copy link
Collaborator

I don't really touch python stuff here.
I've suggested how i think this would be best done, but i'm perfectly happy to just leave it for other reviewers.

@LebedevRI LebedevRI requested review from LebedevRI and removed request for LebedevRI June 27, 2025 20:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants