Skip to content

Commit 90670b5

Browse files
Support: Add proxies for raw_ostream and raw_pwrite_stream (#113362)
Add proxies classes for `raw_ostream` and `raw_pwrite_stream` called `raw_ostream_proxy` and `raw_pwrite_stream_proxy`. Add adaptor classes, `raw_ostream_proxy_adaptor<>` and `raw_pwrite_stream_proxy_adaptor<>`, to allow subclasses to use a different parent class than `raw_ostream` or `raw_pwrite_stream`. The adaptors are used by a future patch to help a subclass of `llvm::vfs::OutputFile`, an abstract subclass of `raw_pwrite_stream`, to proxy a `raw_fd_ostream`. Patched by dexonsmith.
1 parent 3160047 commit 90670b5

File tree

5 files changed

+374
-0
lines changed

5 files changed

+374
-0
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
//===- raw_ostream_proxy.h - Proxies for raw output streams -----*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_SUPPORT_RAW_OSTREAM_PROXY_H
10+
#define LLVM_SUPPORT_RAW_OSTREAM_PROXY_H
11+
12+
#include "llvm/Support/raw_ostream.h"
13+
14+
namespace llvm {
15+
16+
/// Adaptor to create a stream class that proxies another \a raw_ostream.
17+
///
18+
/// Use \a raw_ostream_proxy_adaptor<> directly to implement an abstract
19+
/// derived class of \a raw_ostream as a proxy. Otherwise use \a
20+
/// raw_ostream_proxy.
21+
///
22+
/// Most operations are forwarded to the proxied stream.
23+
///
24+
/// If the proxied stream is buffered, the buffer is dropped and moved to this
25+
/// stream. This allows \a flush() to work correctly, flushing immediately from
26+
/// the proxy through to the final stream, and avoids any wasteful
27+
/// double-buffering.
28+
///
29+
/// \a enable_colors() changes both the proxied stream and the proxy itself.
30+
/// \a is_displayed() and \a has_colors() are forwarded to the proxy. \a
31+
/// changeColor(), resetColor(), and \a reverseColor() are not forwarded, since
32+
/// they need to call \a flush() and the buffer lives in the proxy.
33+
template <class RawOstreamT = raw_ostream>
34+
class raw_ostream_proxy_adaptor : public RawOstreamT {
35+
void write_impl(const char *Ptr, size_t Size) override {
36+
getProxiedOS().write(Ptr, Size);
37+
}
38+
uint64_t current_pos() const override { return getProxiedOS().tell(); }
39+
size_t preferred_buffer_size() const override {
40+
return getPreferredBufferSize();
41+
}
42+
43+
public:
44+
void reserveExtraSpace(uint64_t ExtraSize) override {
45+
getProxiedOS().reserveExtraSpace(ExtraSize);
46+
}
47+
bool is_displayed() const override { return getProxiedOS().is_displayed(); }
48+
bool has_colors() const override { return getProxiedOS().has_colors(); }
49+
void enable_colors(bool enable) override {
50+
RawOstreamT::enable_colors(enable);
51+
getProxiedOS().enable_colors(enable);
52+
}
53+
bool hasProxiedOS() const { return OS; }
54+
raw_ostream &getProxiedOS() const {
55+
assert(OS && "raw_ostream_proxy_adaptor use after reset");
56+
return *OS;
57+
}
58+
size_t getPreferredBufferSize() const { return PreferredBufferSize; }
59+
60+
~raw_ostream_proxy_adaptor() override { resetProxiedOS(); }
61+
62+
protected:
63+
template <class... ArgsT>
64+
explicit raw_ostream_proxy_adaptor(raw_ostream &OS, ArgsT &&...Args)
65+
: RawOstreamT(std::forward<ArgsT>(Args)...), OS(&OS),
66+
PreferredBufferSize(OS.GetBufferSize()) {
67+
// Drop OS's buffer to make this->flush() forward. This proxy will add a
68+
// buffer in its place.
69+
OS.SetUnbuffered();
70+
}
71+
72+
/// Stop proxying the stream. Flush and set up a crash for future writes.
73+
///
74+
/// For example, this can simplify logic when a subclass might have a longer
75+
/// lifetime than the stream it proxies.
76+
void resetProxiedOS() {
77+
this->SetUnbuffered();
78+
OS = nullptr;
79+
}
80+
81+
private:
82+
raw_ostream *OS;
83+
84+
/// Caches the value of OS->GetBufferSize() at construction time.
85+
size_t PreferredBufferSize;
86+
};
87+
88+
/// Adaptor for creating a stream that proxies a \a raw_pwrite_stream.
89+
template <class RawPwriteStreamT = raw_pwrite_stream>
90+
class raw_pwrite_stream_proxy_adaptor
91+
: public raw_ostream_proxy_adaptor<RawPwriteStreamT> {
92+
using RawOstreamAdaptorT = raw_ostream_proxy_adaptor<RawPwriteStreamT>;
93+
94+
void pwrite_impl(const char *Ptr, size_t Size, uint64_t Offset) override {
95+
this->flush();
96+
getProxiedOS().pwrite(Ptr, Size, Offset);
97+
}
98+
99+
protected:
100+
raw_pwrite_stream_proxy_adaptor() = default;
101+
template <class... ArgsT>
102+
explicit raw_pwrite_stream_proxy_adaptor(raw_pwrite_stream &OS,
103+
ArgsT &&...Args)
104+
: RawOstreamAdaptorT(OS, std::forward<ArgsT>(Args)...) {}
105+
106+
raw_pwrite_stream &getProxiedOS() const {
107+
return static_cast<raw_pwrite_stream &>(RawOstreamAdaptorT::getProxiedOS());
108+
}
109+
};
110+
111+
/// Non-owning proxy for a \a raw_ostream. Enables passing a stream into an
112+
/// API that takes ownership.
113+
class raw_ostream_proxy : public raw_ostream_proxy_adaptor<> {
114+
void anchor() override;
115+
116+
public:
117+
raw_ostream_proxy(raw_ostream &OS) : raw_ostream_proxy_adaptor<>(OS) {}
118+
};
119+
120+
/// Non-owning proxy for a \a raw_pwrite_stream. Enables passing a stream
121+
/// into an API that takes ownership.
122+
class raw_pwrite_stream_proxy : public raw_pwrite_stream_proxy_adaptor<> {
123+
void anchor() override;
124+
125+
public:
126+
raw_pwrite_stream_proxy(raw_pwrite_stream &OS)
127+
: raw_pwrite_stream_proxy_adaptor<>(OS) {}
128+
};
129+
130+
} // end namespace llvm
131+
132+
#endif // LLVM_SUPPORT_RAW_OSTREAM_PROXY_H

llvm/lib/Support/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ add_llvm_component_library(LLVMSupport
277277
YAMLTraits.cpp
278278
raw_os_ostream.cpp
279279
raw_ostream.cpp
280+
raw_ostream_proxy.cpp
280281
raw_socket_stream.cpp
281282
regcomp.c
282283
regerror.c
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//===- raw_ostream_proxy.cpp - Implement the raw_ostream proxies ----------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "llvm/Support/raw_ostream_proxy.h"
10+
11+
using namespace llvm;
12+
13+
void raw_ostream_proxy::anchor() {}
14+
15+
void raw_pwrite_stream_proxy::anchor() {}

llvm/unittests/Support/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ add_llvm_unittest(SupportTests
111111
formatted_raw_ostream_test.cpp
112112
raw_fd_stream_test.cpp
113113
raw_ostream_test.cpp
114+
raw_ostream_proxy_test.cpp
114115
raw_pwrite_stream_test.cpp
115116
raw_sha1_ostream_test.cpp
116117
raw_socket_stream_test.cpp
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
//===- raw_ostream_proxy_test.cpp - Tests for raw ostream proxies ---------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "llvm/ADT/SmallString.h"
10+
#include "llvm/Support/WithColor.h"
11+
#include "llvm/Support/raw_ostream.h"
12+
#include "llvm/Support/raw_ostream_proxy.h"
13+
#include "gtest/gtest.h"
14+
15+
using namespace llvm;
16+
17+
namespace {
18+
19+
/// Naive version of raw_svector_ostream that is buffered (by default) and
20+
/// doesn't support pwrite.
21+
class BufferedNoPwriteSmallVectorStream : public raw_ostream {
22+
public:
23+
// Choose a strange buffer size to ensure it doesn't collide with the default
24+
// on \a raw_ostream.
25+
static constexpr size_t PreferredBufferSize = 63;
26+
27+
size_t preferred_buffer_size() const override { return PreferredBufferSize; }
28+
uint64_t current_pos() const override { return Vector.size(); }
29+
void write_impl(const char *Ptr, size_t Size) override {
30+
Vector.append(Ptr, Ptr + Size);
31+
}
32+
33+
bool is_displayed() const override { return IsDisplayed; }
34+
35+
explicit BufferedNoPwriteSmallVectorStream(SmallVectorImpl<char> &Vector)
36+
: Vector(Vector) {}
37+
~BufferedNoPwriteSmallVectorStream() override { flush(); }
38+
39+
SmallVectorImpl<char> &Vector;
40+
bool IsDisplayed = false;
41+
};
42+
43+
constexpr size_t BufferedNoPwriteSmallVectorStream::PreferredBufferSize;
44+
45+
TEST(raw_ostream_proxyTest, write) {
46+
// Besides confirming that "write" works, this test confirms that the proxy
47+
// takes on the buffer from the stream it's proxying, such that writes to the
48+
// proxy are flushed to the underlying stream as if no proxy were present.
49+
SmallString<128> Dest;
50+
{
51+
// Confirm that BufferedNoPwriteSmallVectorStream is buffered by default,
52+
// and that setting up a proxy effectively transfers a buffer of the same
53+
// size to the proxy.
54+
BufferedNoPwriteSmallVectorStream DestOS(Dest);
55+
EXPECT_EQ(BufferedNoPwriteSmallVectorStream::PreferredBufferSize,
56+
DestOS.GetBufferSize());
57+
raw_ostream_proxy ProxyOS(DestOS);
58+
EXPECT_EQ(0u, DestOS.GetBufferSize());
59+
EXPECT_EQ(BufferedNoPwriteSmallVectorStream::PreferredBufferSize,
60+
ProxyOS.GetBufferSize());
61+
62+
// Flushing should send through to Dest.
63+
ProxyOS << "abcd";
64+
EXPECT_EQ("", Dest);
65+
ProxyOS.flush();
66+
EXPECT_EQ("abcd", Dest);
67+
68+
// Buffer should still work.
69+
ProxyOS << "e";
70+
EXPECT_EQ("abcd", Dest);
71+
}
72+
73+
// Destructing ProxyOS should flush (and not crash).
74+
EXPECT_EQ("abcde", Dest);
75+
76+
{
77+
// Set up another stream, this time unbuffered.
78+
BufferedNoPwriteSmallVectorStream DestOS(Dest);
79+
DestOS.SetUnbuffered();
80+
EXPECT_EQ(0u, DestOS.GetBufferSize());
81+
raw_ostream_proxy ProxyOS(DestOS);
82+
EXPECT_EQ(0u, DestOS.GetBufferSize());
83+
EXPECT_EQ(0u, ProxyOS.GetBufferSize());
84+
85+
// Flushing should not be required.
86+
ProxyOS << "f";
87+
EXPECT_EQ("abcdef", Dest);
88+
}
89+
EXPECT_EQ("abcdef", Dest);
90+
}
91+
92+
TEST(raw_ostream_proxyTest, pwrite) {
93+
// This test confirms that the proxy takes on the buffer from the stream it's
94+
// proxying, such that writes to the proxy are flushed to the underlying
95+
// stream as if no proxy were present.
96+
SmallString<128> Dest;
97+
raw_svector_ostream DestOS(Dest);
98+
raw_pwrite_stream_proxy ProxyOS(DestOS);
99+
EXPECT_EQ(0u, ProxyOS.GetBufferSize());
100+
101+
// Get some initial data.
102+
ProxyOS << "abcd";
103+
EXPECT_EQ("abcd", Dest);
104+
105+
// Confirm that pwrite works.
106+
ProxyOS.pwrite("BC", 2, 1);
107+
EXPECT_EQ("aBCd", Dest);
108+
}
109+
110+
TEST(raw_ostream_proxyTest, pwriteWithBuffer) {
111+
// This test confirms that when a buffer is configured, pwrite still works.
112+
SmallString<128> Dest;
113+
raw_svector_ostream DestOS(Dest);
114+
DestOS.SetBufferSize(256);
115+
EXPECT_EQ(256u, DestOS.GetBufferSize());
116+
117+
// Confirm that the proxy steals the buffer.
118+
raw_pwrite_stream_proxy ProxyOS(DestOS);
119+
EXPECT_EQ(0u, DestOS.GetBufferSize());
120+
EXPECT_EQ(256u, ProxyOS.GetBufferSize());
121+
122+
// Check that the buffer is working.
123+
ProxyOS << "abcd";
124+
EXPECT_EQ("", Dest);
125+
126+
// Confirm that pwrite flushes.
127+
ProxyOS.pwrite("BC", 2, 1);
128+
EXPECT_EQ("aBCd", Dest);
129+
}
130+
131+
class ProxyWithReset : public raw_ostream_proxy_adaptor<> {
132+
public:
133+
ProxyWithReset(raw_ostream &OS) : raw_ostream_proxy_adaptor<>(OS) {}
134+
135+
// Allow this to be called outside the class.
136+
using raw_ostream_proxy_adaptor<>::hasProxiedOS;
137+
using raw_ostream_proxy_adaptor<>::getProxiedOS;
138+
using raw_ostream_proxy_adaptor<>::resetProxiedOS;
139+
};
140+
141+
TEST(raw_ostream_proxyTest, resetProxiedOS) {
142+
// Confirm that base classes can drop the proxied OS before destruction and
143+
// get consistent crashes.
144+
SmallString<128> Dest;
145+
BufferedNoPwriteSmallVectorStream DestOS(Dest);
146+
ProxyWithReset ProxyOS(DestOS);
147+
EXPECT_TRUE(ProxyOS.hasProxiedOS());
148+
EXPECT_EQ(&DestOS, &ProxyOS.getProxiedOS());
149+
150+
// Write some data.
151+
ProxyOS << "abcd";
152+
EXPECT_EQ("", Dest);
153+
154+
// Reset the underlying stream.
155+
ProxyOS.resetProxiedOS();
156+
EXPECT_EQ("abcd", Dest);
157+
EXPECT_EQ(0u, ProxyOS.GetBufferSize());
158+
EXPECT_FALSE(ProxyOS.hasProxiedOS());
159+
160+
#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
161+
EXPECT_DEATH(ProxyOS << "e", "use after reset");
162+
EXPECT_DEATH(ProxyOS.getProxiedOS(), "use after reset");
163+
#endif
164+
}
165+
166+
TEST(raw_ostream_proxyTest, ColorMode) {
167+
{
168+
SmallString<128> Dest;
169+
BufferedNoPwriteSmallVectorStream DestOS(Dest);
170+
DestOS.IsDisplayed = true;
171+
raw_ostream_proxy ProxyOS(DestOS);
172+
ProxyOS.enable_colors(true);
173+
174+
WithColor(ProxyOS, raw_ostream::Colors::RED, /*Bold=*/true, /*BG=*/false,
175+
ColorMode::Disable)
176+
<< "test";
177+
EXPECT_EQ("", Dest);
178+
ProxyOS.flush();
179+
EXPECT_EQ("test", Dest);
180+
}
181+
182+
{
183+
SmallString<128> Dest;
184+
BufferedNoPwriteSmallVectorStream DestOS(Dest);
185+
raw_ostream_proxy ProxyOS(DestOS);
186+
187+
WithColor(ProxyOS, raw_ostream::Colors::RED, /*Bold=*/true, /*BG=*/false,
188+
ColorMode::Auto)
189+
<< "test";
190+
EXPECT_EQ("", Dest);
191+
ProxyOS.flush();
192+
EXPECT_EQ("test", Dest);
193+
}
194+
195+
#ifdef LLVM_ON_UNIX
196+
{
197+
SmallString<128> Dest;
198+
BufferedNoPwriteSmallVectorStream DestOS(Dest);
199+
raw_ostream_proxy ProxyOS(DestOS);
200+
ProxyOS.enable_colors(true);
201+
202+
WithColor(ProxyOS, raw_ostream::Colors::RED, /*Bold=*/true, /*BG=*/false,
203+
ColorMode::Enable)
204+
<< "test";
205+
EXPECT_EQ("", Dest);
206+
ProxyOS.flush();
207+
EXPECT_EQ("\x1B[0;1;31mtest\x1B[0m", Dest);
208+
}
209+
210+
{
211+
SmallString<128> Dest;
212+
BufferedNoPwriteSmallVectorStream DestOS(Dest);
213+
DestOS.IsDisplayed = true;
214+
raw_ostream_proxy ProxyOS(DestOS);
215+
ProxyOS.enable_colors(true);
216+
217+
WithColor(ProxyOS, HighlightColor::Error, ColorMode::Auto) << "test";
218+
EXPECT_EQ("", Dest);
219+
ProxyOS.flush();
220+
EXPECT_EQ("\x1B[0;1;31mtest\x1B[0m", Dest);
221+
}
222+
#endif
223+
}
224+
225+
} // end namespace

0 commit comments

Comments
 (0)