diff --git a/upb_generator/BUILD b/upb_generator/BUILD index 56b211d748..757d840af5 100644 --- a/upb_generator/BUILD +++ b/upb_generator/BUILD @@ -409,27 +409,3 @@ proto_lang_toolchain( runtime = "//upb:generated_reflection_support__only_for_generated_code_do_not_use__i_give_permission_to_break_me", visibility = ["//visibility:public"], ) - -cc_binary( - name = "protoc-gen-upbdev", - srcs = [ - "protoc-gen-upbdev.cc", - "subprocess.cc", - "subprocess.h", - ], - copts = UPB_DEFAULT_CPPOPTS, - target_compatible_with = select({ - "@platforms//os:windows": ["@platforms//:incompatible"], - "//conditions:default": [], - }), - visibility = ["//visibility:public"], - deps = [ - ":plugin_upb_proto", - ":upbdev", - "//upb:base", - "//upb:mem", - "//upb:port", - "@com_google_absl//absl/log:absl_log", - "@com_google_absl//absl/strings", - ], -) diff --git a/upb_generator/protoc-gen-upbdev.cc b/upb_generator/protoc-gen-upbdev.cc deleted file mode 100644 index 801a2c8438..0000000000 --- a/upb_generator/protoc-gen-upbdev.cc +++ /dev/null @@ -1,71 +0,0 @@ -// Protocol Buffers - Google's data interchange format -// Copyright 2023 Google LLC. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file or at -// https://developers.google.com/open-source/licenses/bsd - -#include -#include - -#include "google/protobuf/compiler/plugin.upb.h" -#include "upb/base/status.h" -#include "upb/base/string_view.h" -#include "upb/mem/arena.h" -#include "upb_generator/subprocess.h" -#include "upb_generator/upbdev.h" - -static constexpr char kDefaultPlugin[] = "protoc_dart_plugin"; - -int main() { - upb_Arena* a = upb_Arena_New(); - upb_Status status; - upb_Status_Clear(&status); - - // Read (binary) stdin into a string. - const std::string input = {std::istreambuf_iterator(std::cin), - std::istreambuf_iterator()}; - - // Parse the request. - auto inner_request = google_protobuf_compiler_CodeGeneratorRequest_parse( - input.c_str(), input.size(), a); - - // Check the request for a plugin name. - std::string plugin = kDefaultPlugin; - if (google_protobuf_compiler_CodeGeneratorRequest_has_parameter(inner_request)) { - auto param = google_protobuf_compiler_CodeGeneratorRequest_parameter(inner_request); - plugin = std::string(param.data, param.size); - } - - // Wrap the request inside a upb_CodeGeneratorRequest and JSON-encode it. - const upb_StringView sv = - upbdev_ProcessInput(input.data(), input.size(), a, &status); - if (!upb_Status_IsOk(&status)) { - std::cerr << status.msg << '\n'; - return -1; - } - - // Launch the subprocess. - upb::generator::Subprocess subprocess; - subprocess.Start(plugin, upb::generator::Subprocess::SEARCH_PATH); - - // Exchange JSON strings with the subprocess. - const std::string json_request = std::string(sv.data, sv.size); - std::string json_response, error; - const bool ok = subprocess.Communicate(json_request, &json_response, &error); - if (!ok) { - // Dump the JSON request to stderr if we can't launch the next plugin. - std::cerr << json_request << '\n'; - return -1; - } - - // Decode, serialize, and write the JSON response. - upbdev_ProcessOutput(json_response.data(), json_response.size(), a, &status); - if (!upb_Status_IsOk(&status)) { - std::cerr << status.msg << '\n'; - return -1; - } - - upb_Arena_Free(a); - return 0; -} diff --git a/upb_generator/subprocess.cc b/upb_generator/subprocess.cc deleted file mode 100644 index 6831f656b7..0000000000 --- a/upb_generator/subprocess.cc +++ /dev/null @@ -1,444 +0,0 @@ -// Protocol Buffers - Google's data interchange format -// Copyright 2023 Google LLC. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file or at -// https://developers.google.com/open-source/licenses/bsd - -// Shamelessly copied from the protobuf compiler's subprocess.cc -// except this version passes strings instead of Messages. - -#include "upb_generator/subprocess.h" - -#include -#include -#include - -#ifndef _MSVC_LANG -#include -#include -#include -#include -#endif - -#include "absl/log/absl_log.h" -#include "absl/strings/substitute.h" - -// Must be last. -#include "upb/port/def.inc" - -namespace upb { -namespace generator { - -namespace { -char* portable_strdup(const char* s) { - char* ns = (char*)malloc(strlen(s) + 1); - if (ns != nullptr) { - strcpy(ns, s); - } - return ns; -} -} // namespace - -#ifdef _WIN32 - -static void CloseHandleOrDie(HANDLE handle) { - if (!CloseHandle(handle)) { - ABSL_LOG(FATAL) << "CloseHandle: " - << Subprocess::Win32ErrorMessage(GetLastError()); - } -} - -Subprocess::Subprocess() - : process_start_error_(ERROR_SUCCESS), - child_handle_(nullptr), - child_stdin_(nullptr), - child_stdout_(nullptr) {} - -Subprocess::~Subprocess() { - if (child_stdin_ != nullptr) { - CloseHandleOrDie(child_stdin_); - } - if (child_stdout_ != nullptr) { - CloseHandleOrDie(child_stdout_); - } -} - -void Subprocess::Start(const std::string& program, SearchMode search_mode) { - // Create the pipes. - HANDLE stdin_pipe_read; - HANDLE stdin_pipe_write; - HANDLE stdout_pipe_read; - HANDLE stdout_pipe_write; - - if (!CreatePipe(&stdin_pipe_read, &stdin_pipe_write, nullptr, 0)) { - ABSL_LOG(FATAL) << "CreatePipe: " << Win32ErrorMessage(GetLastError()); - } - if (!CreatePipe(&stdout_pipe_read, &stdout_pipe_write, nullptr, 0)) { - ABSL_LOG(FATAL) << "CreatePipe: " << Win32ErrorMessage(GetLastError()); - } - - // Make child side of the pipes inheritable. - if (!SetHandleInformation(stdin_pipe_read, HANDLE_FLAG_INHERIT, - HANDLE_FLAG_INHERIT)) { - ABSL_LOG(FATAL) << "SetHandleInformation: " - << Win32ErrorMessage(GetLastError()); - } - if (!SetHandleInformation(stdout_pipe_write, HANDLE_FLAG_INHERIT, - HANDLE_FLAG_INHERIT)) { - ABSL_LOG(FATAL) << "SetHandleInformation: " - << Win32ErrorMessage(GetLastError()); - } - - // Setup STARTUPINFO to redirect handles. - STARTUPINFOA startup_info; - ZeroMemory(&startup_info, sizeof(startup_info)); - startup_info.cb = sizeof(startup_info); - startup_info.dwFlags = STARTF_USESTDHANDLES; - startup_info.hStdInput = stdin_pipe_read; - startup_info.hStdOutput = stdout_pipe_write; - startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE); - - if (startup_info.hStdError == INVALID_HANDLE_VALUE) { - ABSL_LOG(FATAL) << "GetStdHandle: " << Win32ErrorMessage(GetLastError()); - } - - // Invoking cmd.exe allows for '.bat' files from the path as well as '.exe'. - // Using a malloc'ed string because CreateProcess() can mutate its second - // parameter. - char* command_line = - portable_strdup(("cmd.exe /c \"" + program + "\"").c_str()); - - // Create the process. - PROCESS_INFORMATION process_info; - - if (CreateProcessA((search_mode == SEARCH_PATH) ? nullptr : program.c_str(), - (search_mode == SEARCH_PATH) ? command_line : nullptr, - nullptr, // process security attributes - nullptr, // thread security attributes - TRUE, // inherit handles? - 0, // obscure creation flags - nullptr, // environment (inherit from parent) - nullptr, // current directory (inherit from parent) - &startup_info, &process_info)) { - child_handle_ = process_info.hProcess; - CloseHandleOrDie(process_info.hThread); - child_stdin_ = stdin_pipe_write; - child_stdout_ = stdout_pipe_read; - } else { - process_start_error_ = GetLastError(); - CloseHandleOrDie(stdin_pipe_write); - CloseHandleOrDie(stdout_pipe_read); - } - - CloseHandleOrDie(stdin_pipe_read); - CloseHandleOrDie(stdout_pipe_write); - free(command_line); -} - -bool Subprocess::Communicate(const std::string& input_data, - std::string* output_data, std::string* error) { - if (process_start_error_ != ERROR_SUCCESS) { - *error = Win32ErrorMessage(process_start_error_); - return false; - } - - GOOGLE_CHECK(child_handle_ != nullptr) << "Must call Start() first."; - - int input_pos = 0; - - while (child_stdout_ != nullptr) { - HANDLE handles[2]; - int handle_count = 0; - - if (child_stdin_ != nullptr) { - handles[handle_count++] = child_stdin_; - } - if (child_stdout_ != nullptr) { - handles[handle_count++] = child_stdout_; - } - - DWORD wait_result = - WaitForMultipleObjects(handle_count, handles, FALSE, INFINITE); - - HANDLE signaled_handle = nullptr; - if (wait_result >= WAIT_OBJECT_0 && - wait_result < WAIT_OBJECT_0 + handle_count) { - signaled_handle = handles[wait_result - WAIT_OBJECT_0]; - } else if (wait_result == WAIT_FAILED) { - ABSL_LOG(FATAL) << "WaitForMultipleObjects: " - << Win32ErrorMessage(GetLastError()); - } else { - ABSL_LOG(FATAL) << "WaitForMultipleObjects: Unexpected return code: " - << wait_result; - } - - if (signaled_handle == child_stdin_) { - DWORD n; - if (!WriteFile(child_stdin_, input_data.data() + input_pos, - input_data.size() - input_pos, &n, nullptr)) { - // Child closed pipe. Presumably it will report an error later. - // Pretend we're done for now. - input_pos = input_data.size(); - } else { - input_pos += n; - } - - if (input_pos == input_data.size()) { - // We're done writing. Close. - CloseHandleOrDie(child_stdin_); - child_stdin_ = nullptr; - } - } else if (signaled_handle == child_stdout_) { - char buffer[4096]; - DWORD n; - - if (!ReadFile(child_stdout_, buffer, sizeof(buffer), &n, nullptr)) { - // We're done reading. Close. - CloseHandleOrDie(child_stdout_); - child_stdout_ = nullptr; - } else { - output_data->append(buffer, n); - } - } - } - - if (child_stdin_ != nullptr) { - // Child did not finish reading input before it closed the output. - // Presumably it exited with an error. - CloseHandleOrDie(child_stdin_); - child_stdin_ = nullptr; - } - - DWORD wait_result = WaitForSingleObject(child_handle_, INFINITE); - - if (wait_result == WAIT_FAILED) { - ABSL_LOG(FATAL) << "WaitForSingleObject: " - << Win32ErrorMessage(GetLastError()); - } else if (wait_result != WAIT_OBJECT_0) { - ABSL_LOG(FATAL) << "WaitForSingleObject: Unexpected return code: " - << wait_result; - } - - DWORD exit_code; - if (!GetExitCodeProcess(child_handle_, &exit_code)) { - ABSL_LOG(FATAL) << "GetExitCodeProcess: " - << Win32ErrorMessage(GetLastError()); - } - - CloseHandleOrDie(child_handle_); - child_handle_ = nullptr; - - if (exit_code != 0) { - *error = absl::Substitute("Plugin failed with status code $0.", exit_code); - return false; - } - - return true; -} - -std::string Subprocess::Win32ErrorMessage(DWORD error_code) { - char* message; - - // WTF? - FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - nullptr, error_code, - MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), - (LPSTR)&message, // NOT A BUG! - 0, nullptr); - - std::string result = message; - LocalFree(message); - return result; -} - -// =================================================================== - -#else // _WIN32 - -Subprocess::Subprocess() - : child_pid_(-1), child_stdin_(-1), child_stdout_(-1) {} - -Subprocess::~Subprocess() { - if (child_stdin_ != -1) { - close(child_stdin_); - } - if (child_stdout_ != -1) { - close(child_stdout_); - } -} - -void Subprocess::Start(const std::string& program, SearchMode search_mode) { - // Note that we assume that there are no other threads, thus we don't have to - // do crazy stuff like using socket pairs or avoiding libc locks. - - // [0] is read end, [1] is write end. - int stdin_pipe[2]; - int stdout_pipe[2]; - - int p0 = pipe(stdin_pipe); - int p1 = pipe(stdout_pipe); - UPB_ASSERT(p0 != -1); - UPB_ASSERT(p1 != -1); - - char* argv[2] = {portable_strdup(program.c_str()), nullptr}; - - child_pid_ = fork(); - if (child_pid_ == -1) { - std::cerr << "fork: " << strerror(errno); - } else if (child_pid_ == 0) { - // We are the child. - dup2(stdin_pipe[0], STDIN_FILENO); - dup2(stdout_pipe[1], STDOUT_FILENO); - - close(stdin_pipe[0]); - close(stdin_pipe[1]); - close(stdout_pipe[0]); - close(stdout_pipe[1]); - - switch (search_mode) { - case SEARCH_PATH: - execvp(argv[0], argv); - break; - case EXACT_NAME: - execv(argv[0], argv); - break; - } - - // Write directly to STDERR_FILENO to avoid stdio code paths that may do - // stuff that is unsafe here. - int ignored; - ignored = write(STDERR_FILENO, argv[0], strlen(argv[0])); - const char* message = - ": program not found or is not executable\n" - "Please specify a program using absolute path or make sure " - "the program is available in your PATH system variable\n"; - ignored = write(STDERR_FILENO, message, strlen(message)); - (void)ignored; - - // Must use _exit() rather than exit() to avoid flushing output buffers - // that will also be flushed by the parent. - _exit(1); - } else { - free(argv[0]); - - close(stdin_pipe[0]); - close(stdout_pipe[1]); - - child_stdin_ = stdin_pipe[1]; - child_stdout_ = stdout_pipe[0]; - } -} - -bool Subprocess::Communicate(const std::string& input_data, - std::string* output_data, std::string* error) { - if (child_stdin_ == -1) { - std::cerr << "Must call Start() first." << '\n'; - UPB_ASSERT(child_stdin_ != -1); - } - - // The "sighandler_t" typedef is GNU-specific, so define our own. - typedef void SignalHandler(int); - - // Make sure SIGPIPE is disabled so that if the child dies it doesn't kill us. - SignalHandler* old_pipe_handler = signal(SIGPIPE, SIG_IGN); - - int input_pos = 0; - int max_fd = std::max(child_stdin_, child_stdout_); - - while (child_stdout_ != -1) { - fd_set read_fds; - fd_set write_fds; - FD_ZERO(&read_fds); - FD_ZERO(&write_fds); - if (child_stdout_ != -1) { - FD_SET(child_stdout_, &read_fds); - } - if (child_stdin_ != -1) { - FD_SET(child_stdin_, &write_fds); - } - - if (select(max_fd + 1, &read_fds, &write_fds, nullptr, nullptr) < 0) { - if (errno == EINTR) { - // Interrupted by signal. Try again. - continue; - } else { - std::cerr << "select: " << strerror(errno) << '\n'; - UPB_ASSERT(0); - } - } - - if (child_stdin_ != -1 && FD_ISSET(child_stdin_, &write_fds)) { - int n = write(child_stdin_, input_data.data() + input_pos, - input_data.size() - input_pos); - if (n < 0) { - // Child closed pipe. Presumably it will report an error later. - // Pretend we're done for now. - input_pos = input_data.size(); - } else { - input_pos += n; - } - - if (input_pos == (int)input_data.size()) { - // We're done writing. Close. - close(child_stdin_); - child_stdin_ = -1; - } - } - - if (child_stdout_ != -1 && FD_ISSET(child_stdout_, &read_fds)) { - char buffer[4096]; - int n = read(child_stdout_, buffer, sizeof(buffer)); - - if (n > 0) { - output_data->append(buffer, (size_t)n); - } else { - // We're done reading. Close. - close(child_stdout_); - child_stdout_ = -1; - } - } - } - - if (child_stdin_ != -1) { - // Child did not finish reading input before it closed the output. - // Presumably it exited with an error. - close(child_stdin_); - child_stdin_ = -1; - } - - int status; - while (waitpid(child_pid_, &status, 0) == -1) { - if (errno != EINTR) { - std::cerr << "waitpid: " << strerror(errno) << '\n'; - UPB_ASSERT(0); - } - } - - // Restore SIGPIPE handling. - signal(SIGPIPE, old_pipe_handler); - - if (WIFEXITED(status)) { - if (WEXITSTATUS(status) != 0) { - int error_code = WEXITSTATUS(status); - *error = - absl::Substitute("Plugin failed with status code $0.", error_code); - return false; - } - } else if (WIFSIGNALED(status)) { - int signal = WTERMSIG(status); - *error = absl::Substitute("Plugin killed by signal $0.", signal); - return false; - } else { - *error = "Neither WEXITSTATUS nor WTERMSIG is true?"; - return false; - } - - return true; -} - -#endif // !_WIN32 - -} // namespace generator -} // namespace upb diff --git a/upb_generator/subprocess.h b/upb_generator/subprocess.h deleted file mode 100644 index 731969083f..0000000000 --- a/upb_generator/subprocess.h +++ /dev/null @@ -1,81 +0,0 @@ -// Protocol Buffers - Google's data interchange format -// Copyright 2023 Google LLC. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file or at -// https://developers.google.com/open-source/licenses/bsd - -// Shamelessly copied from the protobuf compiler's subprocess.h -// except this version passes strings instead of Messages. - -#ifndef THIRD_PARTY_UPB_UPB_GENERATOR_H_ -#define THIRD_PARTY_UPB_UPB_GENERATOR_H_ - -#ifdef _WIN32 -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN // right... -#endif -#include -#else // _WIN32 -#include -#include -#endif // !_WIN32 -#include - -namespace upb { -namespace generator { - -// Utility class for launching sub-processes. -class Subprocess { - public: - Subprocess(); - ~Subprocess(); - - enum SearchMode { - SEARCH_PATH, // Use PATH environment variable. - EXACT_NAME // Program is an exact file name; don't use the PATH. - }; - - // Start the subprocess. Currently we don't provide a way to specify - // arguments as protoc plugins don't have any. - void Start(const std::string& program, SearchMode search_mode); - - // Pipe the input message to the subprocess's stdin, then close the pipe. - // Meanwhile, read from the subprocess's stdout and copy into *output. - // All this is done carefully to avoid deadlocks. - // Returns true if successful. On any sort of error, returns false and sets - // *error to a description of the problem. - bool Communicate(const std::string& input_data, std::string* output_data, - std::string* error); - -#ifdef _WIN32 - // Given an error code, returns a human-readable error message. This is - // defined here so that CommandLineInterface can share it. - static std::string Win32ErrorMessage(DWORD error_code); -#endif - - private: -#ifdef _WIN32 - DWORD process_start_error_; - HANDLE child_handle_; - - // The file handles for our end of the child's pipes. We close each and - // set it to NULL when no longer needed. - HANDLE child_stdin_; - HANDLE child_stdout_; - -#else // _WIN32 - pid_t child_pid_; - - // The file descriptors for our end of the child's pipes. We close each and - // set it to -1 when no longer needed. - int child_stdin_; - int child_stdout_; - -#endif // !_WIN32 -}; - -} // namespace generator -} // namespace upb - -#endif // THIRD_PARTY_UPB_UPB_GENERATOR_H_ diff --git a/upb_generator/upbdev.c b/upb_generator/upbdev.c index 1a48621a9b..4246919564 100644 --- a/upb_generator/upbdev.c +++ b/upb_generator/upbdev.c @@ -83,8 +83,9 @@ upb_StringView upbdev_ProcessInput(const char* buf, size_t size, return upbc_JsonEncode(outer_request, arena, status); } -upb_StringView upbdev_ProcessOutput(const char* buf, size_t size, - upb_Arena* arena, upb_Status* status) { +static upb_StringView upbdev_ProcessOutput(const char* buf, size_t size, + upb_Arena* arena, + upb_Status* status) { upb_StringView out = {.data = NULL, .size = 0}; const google_protobuf_compiler_CodeGeneratorResponse* response = diff --git a/upb_generator/upbdev.h b/upb_generator/upbdev.h index ee5b0a8aa9..a5fd0f12aa 100644 --- a/upb_generator/upbdev.h +++ b/upb_generator/upbdev.h @@ -25,11 +25,6 @@ UPB_API upb_StringView upbdev_ProcessInput(const char* buf, size_t size, upb_Arena* arena, upb_Status* status); -// Decode |buf| from JSON, serialize to wire format, and return it. -UPB_API upb_StringView upbdev_ProcessOutput(const char* buf, size_t size, - upb_Arena* arena, - upb_Status* status); - // Decode |buf| from JSON, serialize to wire format, and write it to stdout. UPB_API void upbdev_ProcessStdout(const char* buf, size_t size, upb_Arena* arena, upb_Status* status);