Windows, JNI: use RAII pattern + clean up gotos
Introduce helper classes whose d'tor automatically
release resources (close handles, release Java
object handles, etc) and get rid of the
"goto cleanup" pattern.
--
PiperOrigin-RevId: 144349886
MOS_MIGRATED_REVID=144349886
diff --git a/src/main/native/windows_processes.cc b/src/main/native/windows_processes.cc
index 951ad77..0eb7e5f 100644
--- a/src/main/native/windows_processes.cc
+++ b/src/main/native/windows_processes.cc
@@ -76,6 +76,44 @@
error_("") {}
};
+struct AutoHandle {
+ AutoHandle() : handle(INVALID_HANDLE_VALUE) {}
+ ~AutoHandle() {
+ CloseHandle(handle); // handles INVALID_HANDLE_VALUE
+ handle = INVALID_HANDLE_VALUE;
+ }
+
+ HANDLE handle;
+};
+
+class JavaByteArray {
+ public:
+ JavaByteArray(JNIEnv* env, jbyteArray java_array)
+ : env_(env),
+ array_(java_array),
+ size_(java_array != nullptr ? env->GetArrayLength(java_array) : 0),
+ ptr_(java_array != nullptr ? env->GetByteArrayElements(java_array, NULL)
+ : nullptr) {}
+
+ ~JavaByteArray() {
+ if (array_ != nullptr) {
+ env_->ReleaseByteArrayElements(array_, ptr_, 0);
+ array_ = nullptr;
+ size_ = 0;
+ ptr_ = nullptr;
+ }
+ }
+
+ jsize size() { return size_; }
+ jbyte* ptr() { return ptr_; }
+
+ private:
+ JNIEnv* env_;
+ jbyteArray array_;
+ jsize size_;
+ jbyte* ptr_;
+};
+
static bool NestedJobsSupported() {
OSVERSIONINFOEX version_info;
version_info.dwOSVersionInfoSize = sizeof(version_info);
@@ -113,6 +151,8 @@
return (path.size() > MAX_PATH) ? (std::wstring(L"\\\\?\\") + path) : path;
}
+static jlong PtrAsJlong(void* p) { return reinterpret_cast<jlong>(p); }
+
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_devtools_build_lib_windows_WindowsProcesses_nativeCreateProcess(
JNIEnv* env, jclass clazz, jstring java_argv0, jstring java_argv_rest,
@@ -127,9 +167,6 @@
AddUncPrefixMaybe(GetJavaWstring(env, java_stderr_redirect));
std::string cwd = GetJavaUTFString(env, java_cwd);
- jsize env_size = -1;
- jbyte* env_bytes = NULL;
-
std::unique_ptr<char[]> mutable_commandline(new char[commandline.size() + 1]);
strncpy(mutable_commandline.get(), commandline.c_str(),
commandline.size() + 1);
@@ -140,37 +177,39 @@
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
- HANDLE stdin_process = INVALID_HANDLE_VALUE;
- HANDLE stdout_process = INVALID_HANDLE_VALUE;
- HANDLE stderr_process = INVALID_HANDLE_VALUE;
- HANDLE thread = INVALID_HANDLE_VALUE;
- HANDLE event = INVALID_HANDLE_VALUE;
+ // Standard file handles are closed even if the process was successfully
+ // created. If this was not so, operations on these file handles would not
+ // return immediately if the process is terminated.
+ // Therefore we make these handles auto-closing (by using AutoHandle).
+ AutoHandle stdin_process;
+ AutoHandle stdout_process;
+ AutoHandle stderr_process;
+ AutoHandle thread;
PROCESS_INFORMATION process_info = {0};
STARTUPINFOA startup_info = {0};
JOBOBJECT_EXTENDED_LIMIT_INFORMATION job_info = {0};
- if (java_env != NULL) {
- env_size = env->GetArrayLength(java_env);
- env_bytes = env->GetByteArrayElements(java_env, NULL);
-
- if (env_size < 2) {
+ JavaByteArray env_map(env, java_env);
+ if (env_map.ptr() != nullptr) {
+ if (env_map.size() < 2) {
result->error_ = "The environment must be at least two bytes long";
- goto cleanup;
- } else if (env_bytes[env_size - 1] != 0 || env_bytes[env_size - 2] != 0) {
+ return PtrAsJlong(result);
+ } else if (env_map.ptr()[env_map.size() - 1] != 0 ||
+ env_map.ptr()[env_map.size() - 2] != 0) {
result->error_ = "Environment array must end with two null bytes";
- goto cleanup;
+ return PtrAsJlong(result);
}
}
- if (!CreatePipe(&stdin_process, &result->stdin_, &sa, 0)) {
+ if (!CreatePipe(&stdin_process.handle, &result->stdin_, &sa, 0)) {
result->error_ = windows_util::GetLastErrorString("CreatePipe(stdin)");
- goto cleanup;
+ return PtrAsJlong(result);
}
if (!stdout_redirect.empty()) {
result->stdout_.close();
- stdout_process = CreateFileW(
+ stdout_process.handle = CreateFileW(
/* lpFileName */ stdout_redirect.c_str(),
/* dwDesiredAccess */ FILE_APPEND_DATA,
/* dwShareMode */ 0,
@@ -179,23 +218,37 @@
/* dwFlagsAndAttributes */ FILE_ATTRIBUTE_NORMAL,
/* hTemplateFile */ NULL);
- if (stdout_process == INVALID_HANDLE_VALUE) {
+ if (stdout_process.handle == INVALID_HANDLE_VALUE) {
result->error_ = windows_util::GetLastErrorString("CreateFile(stdout)");
- goto cleanup;
+ return PtrAsJlong(result);
}
} else {
- if (!CreatePipe(&result->stdout_.handle_, &stdout_process, &sa, 0)) {
+ if (!CreatePipe(&result->stdout_.handle_, &stdout_process.handle, &sa, 0)) {
result->error_ = windows_util::GetLastErrorString("CreatePipe(stdout)");
- goto cleanup;
+ return PtrAsJlong(result);
}
}
+ // The value of the stderr HANDLE.
+ // If stdout and stderr are redirected to the same file, this will be equal to
+ // stdout_process.handle and stderr_process.handle will be
+ // INVALID_HANDLE_VALUE, so the two AutoHandle objects' d'tors will not
+ // attempt to close stdout's handle twice.
+ // If stdout != stderr, then stderr_handle = stderr_process.handle, and these
+ // are distinct from stdout_process.handle, so again the d'tors will do the
+ // right thing, closing the handles.
+ // In both cases, we DO NOT close stderr_handle, since it's either
+ // stdout_process's or stderr_process's d'tor doing so.
+ HANDLE stderr_handle = INVALID_HANDLE_VALUE;
+
if (!stderr_redirect.empty()) {
result->stderr_.close();
if (stdout_redirect == stderr_redirect) {
- stderr_process = stdout_process;
+ stderr_handle = stdout_process.handle;
+ // do not set stderr_process.handle; it equals stdout_process.handle and
+ // the AutoHandle d'tor would attempt to close it again
} else {
- stderr_process = CreateFileW(
+ stderr_handle = CreateFileW(
/* lpFileName */ stderr_redirect.c_str(),
/* dwDesiredAccess */ FILE_APPEND_DATA,
/* dwShareMode */ 0,
@@ -204,16 +257,20 @@
/* dwFlagsAndAttributes */ FILE_ATTRIBUTE_NORMAL,
/* hTemplateFile */ NULL);
- if (stderr_process == INVALID_HANDLE_VALUE) {
+ if (stderr_handle == INVALID_HANDLE_VALUE) {
result->error_ = windows_util::GetLastErrorString("CreateFile(stderr)");
- goto cleanup;
+ return PtrAsJlong(result);
}
+ // stderr_process != stdout_process, so set its handle, so the AutoHandle
+ // d'tor will close it
+ stderr_process.handle = stderr_handle;
}
} else {
- if (!CreatePipe(&result->stderr_.handle_, &stderr_process, &sa, 0)) {
+ if (!CreatePipe(&result->stderr_.handle_, &stderr_handle, &sa, 0)) {
result->error_ = windows_util::GetLastErrorString("CreatePipe(stderr)");
- goto cleanup;
+ return PtrAsJlong(result);
}
+ stderr_process.handle = stderr_handle;
}
// MDSN says that the default for job objects is that breakaway is not
@@ -221,7 +278,7 @@
HANDLE job = CreateJobObject(NULL, NULL);
if (job == NULL) {
result->error_ = windows_util::GetLastErrorString("CreateJobObject()");
- goto cleanup;
+ return PtrAsJlong(result);
}
result->job_ = job;
@@ -235,12 +292,12 @@
sizeof(job_info))) {
result->error_ =
windows_util::GetLastErrorString("SetInformationJobObject()");
- goto cleanup;
+ return PtrAsJlong(result);
}
- startup_info.hStdInput = stdin_process;
- startup_info.hStdOutput = stdout_process;
- startup_info.hStdError = stderr_process;
+ startup_info.hStdInput = stdin_process.handle;
+ startup_info.hStdOutput = stdout_process.handle;
+ startup_info.hStdError = stderr_handle;
startup_info.dwFlags |= STARTF_USESTDHANDLES;
BOOL ok = CreateProcessA(
@@ -252,19 +309,19 @@
/* dwCreationFlags */ CREATE_NO_WINDOW // Don't create a console window
| CREATE_NEW_PROCESS_GROUP // So that Ctrl-Break is not propagated
| CREATE_SUSPENDED, // So that it doesn't start a new job itself
- /* lpEnvironment */ env_bytes,
+ /* lpEnvironment */ env_map.ptr(),
/* lpCurrentDirectory */ cwd.empty() ? nullptr : cwd.c_str(),
/* lpStartupInfo */ &startup_info,
/* lpProcessInformation */ &process_info);
if (!ok) {
result->error_ = windows_util::GetLastErrorString("CreateProcess()");
- goto cleanup;
+ return PtrAsJlong(result);
}
result->pid_ = process_info.dwProcessId;
result->process_ = process_info.hProcess;
- thread = process_info.hThread;
+ thread.handle = process_info.hThread;
if (!AssignProcessToJobObject(result->job_, result->process_)) {
BOOL is_in_job = false;
@@ -280,44 +337,18 @@
} else {
result->error_ =
windows_util::GetLastErrorString("AssignProcessToJobObject()");
- goto cleanup;
+ return PtrAsJlong(result);
}
}
// Now that we put the process in a new job object, we can start executing it
- if (ResumeThread(thread) == -1) {
+ if (ResumeThread(thread.handle) == -1) {
result->error_ = windows_util::GetLastErrorString("ResumeThread()");
- goto cleanup;
+ return PtrAsJlong(result);
}
result->error_ = "";
-
-cleanup:
- // Standard file handles are closed even if the process was successfully
- // created. If this was not so, operations on these file handles would not
- // return immediately if the process is terminated.
- if (stdin_process != INVALID_HANDLE_VALUE) {
- CloseHandle(stdin_process);
- }
-
- if (stdout_process != INVALID_HANDLE_VALUE) {
- CloseHandle(stdout_process);
- }
-
- if (stderr_process != INVALID_HANDLE_VALUE
- && stderr_process != stdout_process) {
- CloseHandle(stderr_process);
- }
-
- if (thread != INVALID_HANDLE_VALUE) {
- CloseHandle(thread);
- }
-
- if (env_bytes != NULL) {
- env->ReleaseByteArrayElements(java_env, env_bytes, 0);
- }
-
- return reinterpret_cast<jlong>(result);
+ return PtrAsJlong(result);
}
extern "C" JNIEXPORT jint JNICALL
@@ -326,22 +357,20 @@
jint offset, jint length) {
NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long);
- jsize array_size = env->GetArrayLength(java_bytes);
- if (offset < 0 || length <= 0 || offset > array_size - length) {
+ JavaByteArray bytes(env, java_bytes);
+ if (offset < 0 || length <= 0 || offset > bytes.size() - length) {
process->error_ = "Array index out of bounds";
return -1;
}
- jbyte* bytes = env->GetByteArrayElements(java_bytes, NULL);
DWORD bytes_written;
- if (!::WriteFile(process->stdin_, bytes + offset, length, &bytes_written,
- NULL)) {
+ if (!::WriteFile(process->stdin_, bytes.ptr() + offset, length,
+ &bytes_written, NULL)) {
process->error_ = windows_util::GetLastErrorString("WriteFile()");
bytes_written = -1;
}
- env->ReleaseByteArrayElements(java_bytes, bytes, 0);
process->error_ = "";
return bytes_written;
}
@@ -350,14 +379,14 @@
Java_com_google_devtools_build_lib_windows_WindowsProcesses_nativeGetStdout(
JNIEnv* env, jclass clazz, jlong process_long) {
NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long);
- return reinterpret_cast<jlong>(&process->stdout_);
+ return PtrAsJlong(&process->stdout_);
}
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_devtools_build_lib_windows_WindowsProcesses_nativeGetStderr(
JNIEnv* env, jclass clazz, jlong process_long) {
NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long);
- return reinterpret_cast<jlong>(&process->stderr_);
+ return PtrAsJlong(&process->stderr_);
}
extern "C" JNIEXPORT jint JNICALL
@@ -367,8 +396,8 @@
NativeOutputStream* stream =
reinterpret_cast<NativeOutputStream*>(stream_long);
- jsize array_size = env->GetArrayLength(java_bytes);
- if (offset < 0 || length <= 0 || offset > array_size - length) {
+ JavaByteArray bytes(env, java_bytes);
+ if (offset < 0 || length <= 0 || offset > bytes.size() - length) {
stream->error_ = "Array index out of bounds";
return -1;
}
@@ -378,9 +407,9 @@
return 0;
}
- jbyte* bytes = env->GetByteArrayElements(java_bytes, NULL);
DWORD bytes_read;
- if (!::ReadFile(stream->handle_, bytes + offset, length, &bytes_read, NULL)) {
+ if (!::ReadFile(stream->handle_, bytes.ptr() + offset, length, &bytes_read,
+ NULL)) {
// Check if either the other end closed the pipe or we did it with
// NativeOutputStream.close() . In the latter case, we'll get a "system
// call interrupted" error.
@@ -396,7 +425,6 @@
stream->error_ = "";
}
- env->ReleaseByteArrayElements(java_bytes, bytes, 0);
return bytes_read;
}