mirror of
https://github.com/Kitware/CMake.git
synced 2025-06-20 03:38:05 +08:00

Some static analysis tools throw a false positive for an out-of-bounds item that is being dereferenced. This out-of-bounds error will never actually happen because of how cmUVProcessChain::InternalData::AddCommand() is being called. Nevertheless, this change adds an assert() to help static analysis tools be absolutely certain that the referenced item is within the vector's bounds. This change also changes the item access to use an index rather than an iterator.
396 lines
9.5 KiB
C++
396 lines
9.5 KiB
C++
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
file Copyright.txt or https://cmake.org/licensing for details. */
|
|
#include "cmUVProcessChain.h"
|
|
|
|
#include "cmAlgorithms.h"
|
|
#include "cmGetPipes.h"
|
|
#include "cmUVHandlePtr.h"
|
|
#include "cmUVStreambuf.h"
|
|
#include "cm_uv.h"
|
|
|
|
#include <assert.h>
|
|
|
|
#include <iterator>
|
|
#include <memory>
|
|
#include <utility>
|
|
|
|
struct cmUVProcessChain::InternalData
|
|
{
|
|
struct BasicStreamData
|
|
{
|
|
cmUVStreambuf Streambuf;
|
|
cm::uv_pipe_ptr BuiltinStream;
|
|
uv_stdio_container_t Stdio;
|
|
};
|
|
|
|
template <typename IOStream>
|
|
struct StreamData : public BasicStreamData
|
|
{
|
|
StreamData()
|
|
: BuiltinIOStream(&this->Streambuf)
|
|
{
|
|
}
|
|
|
|
IOStream BuiltinIOStream;
|
|
|
|
IOStream* GetBuiltinStream()
|
|
{
|
|
if (this->BuiltinStream.get()) {
|
|
return &this->BuiltinIOStream;
|
|
}
|
|
return nullptr;
|
|
}
|
|
};
|
|
|
|
struct ProcessData
|
|
{
|
|
cmUVProcessChain::InternalData* Data;
|
|
cm::uv_process_ptr Process;
|
|
cm::uv_pipe_ptr OutputPipe;
|
|
bool Finished = false;
|
|
Status ProcessStatus;
|
|
};
|
|
|
|
const cmUVProcessChainBuilder* Builder = nullptr;
|
|
|
|
bool Valid = false;
|
|
|
|
cm::uv_loop_ptr Loop;
|
|
|
|
StreamData<std::istream> OutputStreamData;
|
|
StreamData<std::istream> ErrorStreamData;
|
|
|
|
unsigned int ProcessesCompleted = 0;
|
|
std::vector<std::unique_ptr<ProcessData>> Processes;
|
|
|
|
bool Prepare(const cmUVProcessChainBuilder* builder);
|
|
bool AddCommand(const cmUVProcessChainBuilder::ProcessConfiguration& config,
|
|
bool first, bool last);
|
|
bool Finish();
|
|
|
|
static const Status* GetStatus(const ProcessData& data);
|
|
};
|
|
|
|
cmUVProcessChainBuilder::cmUVProcessChainBuilder()
|
|
{
|
|
this->SetNoStream(Stream_INPUT)
|
|
.SetNoStream(Stream_OUTPUT)
|
|
.SetNoStream(Stream_ERROR);
|
|
}
|
|
|
|
cmUVProcessChainBuilder& cmUVProcessChainBuilder::AddCommand(
|
|
const std::vector<std::string>& arguments)
|
|
{
|
|
if (!arguments.empty()) {
|
|
this->Processes.emplace_back();
|
|
this->Processes.back().Arguments = arguments;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetNoStream(Stream stdio)
|
|
{
|
|
switch (stdio) {
|
|
case Stream_INPUT:
|
|
case Stream_OUTPUT:
|
|
case Stream_ERROR: {
|
|
auto& streamData = this->Stdio[stdio];
|
|
streamData.Type = None;
|
|
break;
|
|
}
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetBuiltinStream(
|
|
Stream stdio)
|
|
{
|
|
switch (stdio) {
|
|
case Stream_INPUT:
|
|
// FIXME
|
|
break;
|
|
|
|
case Stream_OUTPUT:
|
|
case Stream_ERROR: {
|
|
auto& streamData = this->Stdio[stdio];
|
|
streamData.Type = Builtin;
|
|
break;
|
|
}
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetExternalStream(
|
|
Stream stdio, int fd)
|
|
{
|
|
switch (stdio) {
|
|
case Stream_INPUT:
|
|
// FIXME
|
|
break;
|
|
|
|
case Stream_OUTPUT:
|
|
case Stream_ERROR: {
|
|
auto& streamData = this->Stdio[stdio];
|
|
streamData.Type = External;
|
|
streamData.FileDescriptor = fd;
|
|
break;
|
|
}
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
cmUVProcessChain cmUVProcessChainBuilder::Start() const
|
|
{
|
|
cmUVProcessChain chain;
|
|
|
|
if (!chain.Data->Prepare(this)) {
|
|
return chain;
|
|
}
|
|
|
|
for (auto it = this->Processes.begin(); it != this->Processes.end(); ++it) {
|
|
if (!chain.Data->AddCommand(*it, it == this->Processes.begin(),
|
|
it == std::prev(this->Processes.end()))) {
|
|
return chain;
|
|
}
|
|
}
|
|
|
|
chain.Data->Finish();
|
|
|
|
return chain;
|
|
}
|
|
|
|
const cmUVProcessChain::Status* cmUVProcessChain::InternalData::GetStatus(
|
|
const cmUVProcessChain::InternalData::ProcessData& data)
|
|
{
|
|
if (data.Finished) {
|
|
return &data.ProcessStatus;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool cmUVProcessChain::InternalData::Prepare(
|
|
const cmUVProcessChainBuilder* builder)
|
|
{
|
|
this->Builder = builder;
|
|
|
|
auto const& output =
|
|
this->Builder->Stdio[cmUVProcessChainBuilder::Stream_OUTPUT];
|
|
auto& outputData = this->OutputStreamData;
|
|
switch (output.Type) {
|
|
case cmUVProcessChainBuilder::None:
|
|
outputData.Stdio.flags = UV_IGNORE;
|
|
break;
|
|
|
|
case cmUVProcessChainBuilder::Builtin:
|
|
outputData.BuiltinStream.init(*this->Loop, 0);
|
|
outputData.Stdio.flags =
|
|
static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
|
|
outputData.Stdio.data.stream = outputData.BuiltinStream;
|
|
break;
|
|
|
|
case cmUVProcessChainBuilder::External:
|
|
outputData.Stdio.flags = UV_INHERIT_FD;
|
|
outputData.Stdio.data.fd = output.FileDescriptor;
|
|
break;
|
|
}
|
|
|
|
auto const& error =
|
|
this->Builder->Stdio[cmUVProcessChainBuilder::Stream_ERROR];
|
|
auto& errorData = this->ErrorStreamData;
|
|
switch (error.Type) {
|
|
case cmUVProcessChainBuilder::None:
|
|
errorData.Stdio.flags = UV_IGNORE;
|
|
break;
|
|
|
|
case cmUVProcessChainBuilder::Builtin: {
|
|
int pipeFd[2];
|
|
if (cmGetPipes(pipeFd) < 0) {
|
|
return false;
|
|
}
|
|
|
|
errorData.BuiltinStream.init(*this->Loop, 0);
|
|
if (uv_pipe_open(errorData.BuiltinStream, pipeFd[0]) < 0) {
|
|
return false;
|
|
}
|
|
errorData.Stdio.flags = UV_INHERIT_FD;
|
|
errorData.Stdio.data.fd = pipeFd[1];
|
|
break;
|
|
}
|
|
|
|
case cmUVProcessChainBuilder::External:
|
|
errorData.Stdio.flags = UV_INHERIT_FD;
|
|
errorData.Stdio.data.fd = error.FileDescriptor;
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool cmUVProcessChain::InternalData::AddCommand(
|
|
const cmUVProcessChainBuilder::ProcessConfiguration& config, bool first,
|
|
bool last)
|
|
{
|
|
this->Processes.emplace_back(cm::make_unique<ProcessData>());
|
|
auto& process = *this->Processes.back();
|
|
process.Data = this;
|
|
|
|
auto options = uv_process_options_t();
|
|
|
|
// Bounds were checked at add time, first element is guaranteed to exist
|
|
options.file = config.Arguments[0].c_str();
|
|
|
|
std::vector<const char*> arguments;
|
|
for (auto const& arg : config.Arguments) {
|
|
arguments.push_back(arg.c_str());
|
|
}
|
|
arguments.push_back(nullptr);
|
|
options.args = const_cast<char**>(arguments.data());
|
|
options.flags = UV_PROCESS_WINDOWS_HIDE;
|
|
|
|
std::array<uv_stdio_container_t, 3> stdio;
|
|
stdio[0] = uv_stdio_container_t();
|
|
if (first) {
|
|
stdio[0].flags = UV_IGNORE;
|
|
} else {
|
|
assert(this->Processes.size() >= 2);
|
|
auto& prev = *this->Processes[this->Processes.size() - 2];
|
|
stdio[0].flags = UV_INHERIT_STREAM;
|
|
stdio[0].data.stream = prev.OutputPipe;
|
|
}
|
|
if (last) {
|
|
stdio[1] = this->OutputStreamData.Stdio;
|
|
} else {
|
|
if (process.OutputPipe.init(*this->Loop, 0) < 0) {
|
|
return false;
|
|
}
|
|
stdio[1] = uv_stdio_container_t();
|
|
stdio[1].flags =
|
|
static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
|
|
stdio[1].data.stream = process.OutputPipe;
|
|
}
|
|
stdio[2] = this->ErrorStreamData.Stdio;
|
|
|
|
options.stdio = stdio.data();
|
|
options.stdio_count = 3;
|
|
options.exit_cb = [](uv_process_t* handle, int64_t exitStatus,
|
|
int termSignal) {
|
|
auto* processData = static_cast<ProcessData*>(handle->data);
|
|
processData->Finished = true;
|
|
processData->ProcessStatus.ExitStatus = exitStatus;
|
|
processData->ProcessStatus.TermSignal = termSignal;
|
|
processData->Data->ProcessesCompleted++;
|
|
};
|
|
|
|
return process.Process.spawn(*this->Loop, options, &process) >= 0;
|
|
}
|
|
|
|
bool cmUVProcessChain::InternalData::Finish()
|
|
{
|
|
if (this->Builder->Stdio[cmUVProcessChainBuilder::Stream_OUTPUT].Type ==
|
|
cmUVProcessChainBuilder::Builtin) {
|
|
this->OutputStreamData.Streambuf.open(
|
|
this->OutputStreamData.BuiltinStream);
|
|
}
|
|
|
|
if (this->Builder->Stdio[cmUVProcessChainBuilder::Stream_ERROR].Type ==
|
|
cmUVProcessChainBuilder::Builtin) {
|
|
cm::uv_pipe_ptr tmpPipe;
|
|
if (tmpPipe.init(*this->Loop, 0) < 0) {
|
|
return false;
|
|
}
|
|
if (uv_pipe_open(tmpPipe, this->ErrorStreamData.Stdio.data.fd) < 0) {
|
|
return false;
|
|
}
|
|
tmpPipe.reset();
|
|
|
|
this->ErrorStreamData.Streambuf.open(this->ErrorStreamData.BuiltinStream);
|
|
}
|
|
|
|
this->Valid = true;
|
|
return true;
|
|
}
|
|
|
|
cmUVProcessChain::cmUVProcessChain()
|
|
: Data(cm::make_unique<InternalData>())
|
|
{
|
|
this->Data->Loop.init();
|
|
}
|
|
|
|
cmUVProcessChain::cmUVProcessChain(cmUVProcessChain&& other) noexcept
|
|
: Data(std::move(other.Data))
|
|
{
|
|
}
|
|
|
|
cmUVProcessChain::~cmUVProcessChain() = default;
|
|
|
|
cmUVProcessChain& cmUVProcessChain::operator=(
|
|
cmUVProcessChain&& other) noexcept
|
|
{
|
|
this->Data = std::move(other.Data);
|
|
return *this;
|
|
}
|
|
|
|
uv_loop_t& cmUVProcessChain::GetLoop()
|
|
{
|
|
return *this->Data->Loop;
|
|
}
|
|
|
|
std::istream* cmUVProcessChain::OutputStream()
|
|
{
|
|
return this->Data->OutputStreamData.GetBuiltinStream();
|
|
}
|
|
|
|
std::istream* cmUVProcessChain::ErrorStream()
|
|
{
|
|
return this->Data->ErrorStreamData.GetBuiltinStream();
|
|
}
|
|
|
|
bool cmUVProcessChain::Valid() const
|
|
{
|
|
return this->Data->Valid;
|
|
}
|
|
|
|
bool cmUVProcessChain::Wait(int64_t milliseconds)
|
|
{
|
|
bool timeout = false;
|
|
cm::uv_timer_ptr timer;
|
|
|
|
if (milliseconds >= 0) {
|
|
timer.init(*this->Data->Loop, &timeout);
|
|
timer.start(
|
|
[](uv_timer_t* handle) {
|
|
auto* timeoutPtr = static_cast<bool*>(handle->data);
|
|
*timeoutPtr = true;
|
|
},
|
|
milliseconds, 0);
|
|
}
|
|
|
|
while (!timeout &&
|
|
this->Data->ProcessesCompleted < this->Data->Processes.size()) {
|
|
uv_run(this->Data->Loop, UV_RUN_ONCE);
|
|
}
|
|
|
|
return !timeout;
|
|
}
|
|
|
|
std::vector<const cmUVProcessChain::Status*> cmUVProcessChain::GetStatus()
|
|
const
|
|
{
|
|
std::vector<const cmUVProcessChain::Status*> statuses(
|
|
this->Data->Processes.size(), nullptr);
|
|
for (std::size_t i = 0; i < statuses.size(); i++) {
|
|
statuses[i] = this->GetStatus(i);
|
|
}
|
|
return statuses;
|
|
}
|
|
|
|
const cmUVProcessChain::Status* cmUVProcessChain::GetStatus(
|
|
std::size_t index) const
|
|
{
|
|
auto const& process = *this->Data->Processes[index];
|
|
if (process.Finished) {
|
|
return &process.ProcessStatus;
|
|
}
|
|
return nullptr;
|
|
}
|