import changes from `dev' branch of rmottola/Arctic-Fox:

- Bug 1166504 - Make nsMultiplexInputStream cloneable. r=bkelly,froydnj (e3410f3a9)
- Bug 1161240 - Make sure that NS_CloneInputStream correctly deals with null input; r=froydnj (b8afd03ec)
- Bug 1059081 - Fix non-unified bustage. a=bustage (135927526)
- Bug 1059081 - More non-unified bustage fixage. (d8fd4ad65)
- Bug 1059081 - Add a threadsafe wrapper for persistent nsMultiplexStream queues. r=nfroyd (1060af97f)
- Bug 1163029 - Use NamedDecl::getName instead of expensive NamedDecl::getNameAsString. r=ehsan (97a8a32a6)
- Bug 1156084 - Disallow AddRef() and Release() calls on the return value of methods returning XPCOM objects; r=jrmuizel (96f6567c8)
This commit is contained in:
2021-05-18 10:21:18 +08:00
parent 963b86a51f
commit 8aa25c2dfc
12 changed files with 259 additions and 19 deletions
+11 -3
View File
@@ -487,8 +487,8 @@ bool classHasAddRefRelease(const CXXRecordDecl *D) {
bool seenAddRef = false;
bool seenRelease = false;
for (CXXRecordDecl::method_iterator method = D->method_begin();
method != D->method_end(); ++method) {
std::string name = method->getNameAsString();
method != D->method_end(); ++method) {
const auto &name = method->getName();
if (name == "AddRef") {
seenAddRef = true;
} else if (name == "Release") {
@@ -743,7 +743,7 @@ AST_MATCHER(MemberExpr, isAddRefOrRelease) {
ValueDecl *Member = Node.getMemberDecl();
CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(Member);
if (Method) {
std::string Name = Method->getNameAsString();
const auto &Name = Method->getName();
return Name == "AddRef" || Name == "Release";
}
return false;
@@ -890,11 +890,19 @@ DiagnosticsMatcher::DiagnosticsMatcher()
)).bind("node"),
&nanExprChecker);
// First, look for direct parents of the MemberExpr.
astMatcher.addMatcher(callExpr(callee(functionDecl(hasNoAddRefReleaseOnReturnAttr()).bind("func")),
hasParent(memberExpr(isAddRefOrRelease(),
hasParent(callExpr())).bind("member")
)).bind("node"),
&noAddRefReleaseOnReturnChecker);
// Then, look for MemberExpr that need to be casted to the right type using
// an intermediary CastExpr before we get to the CallExpr.
astMatcher.addMatcher(callExpr(callee(functionDecl(hasNoAddRefReleaseOnReturnAttr()).bind("func")),
hasParent(castExpr(hasParent(memberExpr(isAddRefOrRelease(),
hasParent(callExpr())).bind("member"))))
).bind("node"),
&noAddRefReleaseOnReturnChecker);
// Match declrefs with type "pointer to object of ref-counted type" inside a
// lambda, where the declaration they reference is not inside the lambda.
@@ -6,12 +6,20 @@ struct Test {
void foo();
};
struct TestD : Test {};
struct S {
Test* f() MOZ_NO_ADDREF_RELEASE_ON_RETURN;
Test& g() MOZ_NO_ADDREF_RELEASE_ON_RETURN;
Test h() MOZ_NO_ADDREF_RELEASE_ON_RETURN;
};
struct SD {
TestD* f() MOZ_NO_ADDREF_RELEASE_ON_RETURN;
TestD& g() MOZ_NO_ADDREF_RELEASE_ON_RETURN;
TestD h() MOZ_NO_ADDREF_RELEASE_ON_RETURN;
};
template<class T>
struct X {
T* f() MOZ_NO_ADDREF_RELEASE_ON_RETURN;
@@ -28,6 +36,10 @@ Test* f() MOZ_NO_ADDREF_RELEASE_ON_RETURN;
Test& g() MOZ_NO_ADDREF_RELEASE_ON_RETURN;
Test h() MOZ_NO_ADDREF_RELEASE_ON_RETURN;
TestD* fd() MOZ_NO_ADDREF_RELEASE_ON_RETURN;
TestD& gd() MOZ_NO_ADDREF_RELEASE_ON_RETURN;
TestD hd() MOZ_NO_ADDREF_RELEASE_ON_RETURN;
void test() {
S s;
s.f()->AddRef(); // expected-error{{'AddRef' cannot be called on the return value of 'f'}}
@@ -39,6 +51,16 @@ void test() {
s.h().AddRef(); // expected-error{{'AddRef' cannot be called on the return value of 'h'}}
s.h().Release(); // expected-error{{'Release' cannot be called on the return value of 'h'}}
s.h().foo();
SD sd;
sd.f()->AddRef(); // expected-error{{'AddRef' cannot be called on the return value of 'f'}}
sd.f()->Release(); // expected-error{{'Release' cannot be called on the return value of 'f'}}
sd.f()->foo();
sd.g().AddRef(); // expected-error{{'AddRef' cannot be called on the return value of 'g'}}
sd.g().Release(); // expected-error{{'Release' cannot be called on the return value of 'g'}}
sd.g().foo();
sd.h().AddRef(); // expected-error{{'AddRef' cannot be called on the return value of 'h'}}
sd.h().Release(); // expected-error{{'Release' cannot be called on the return value of 'h'}}
sd.h().foo();
X<Test> x;
x.f()->AddRef(); // expected-error{{'AddRef' cannot be called on the return value of 'f'}}
x.f()->Release(); // expected-error{{'Release' cannot be called on the return value of 'f'}}
@@ -49,10 +71,24 @@ void test() {
x.h().AddRef(); // expected-error{{'AddRef' cannot be called on the return value of 'h'}}
x.h().Release(); // expected-error{{'Release' cannot be called on the return value of 'h'}}
x.h().foo();
X<TestD> xd;
xd.f()->AddRef(); // expected-error{{'AddRef' cannot be called on the return value of 'f'}}
xd.f()->Release(); // expected-error{{'Release' cannot be called on the return value of 'f'}}
xd.f()->foo();
xd.g().AddRef(); // expected-error{{'AddRef' cannot be called on the return value of 'g'}}
xd.g().Release(); // expected-error{{'Release' cannot be called on the return value of 'g'}}
xd.g().foo();
xd.h().AddRef(); // expected-error{{'AddRef' cannot be called on the return value of 'h'}}
xd.h().Release(); // expected-error{{'Release' cannot be called on the return value of 'h'}}
xd.h().foo();
SP<Test> sp;
sp->AddRef(); // expected-error{{'AddRef' cannot be called on the return value of 'operator->'}}
sp->Release(); // expected-error{{'Release' cannot be called on the return value of 'operator->'}}
sp->foo();
SP<TestD> spd;
spd->AddRef(); // expected-error{{'AddRef' cannot be called on the return value of 'operator->'}}
spd->Release(); // expected-error{{'Release' cannot be called on the return value of 'operator->'}}
spd->foo();
f()->AddRef(); // expected-error{{'AddRef' cannot be called on the return value of 'f'}}
f()->Release(); // expected-error{{'Release' cannot be called on the return value of 'f'}}
f()->foo();
@@ -62,4 +98,13 @@ void test() {
h().AddRef(); // expected-error{{'AddRef' cannot be called on the return value of 'h'}}
h().Release(); // expected-error{{'Release' cannot be called on the return value of 'h'}}
h().foo();
fd()->AddRef(); // expected-error{{'AddRef' cannot be called on the return value of 'fd'}}
fd()->Release(); // expected-error{{'Release' cannot be called on the return value of 'fd'}}
fd()->foo();
gd().AddRef(); // expected-error{{'AddRef' cannot be called on the return value of 'gd'}}
gd().Release(); // expected-error{{'Release' cannot be called on the return value of 'gd'}}
gd().foo();
hd().AddRef(); // expected-error{{'AddRef' cannot be called on the return value of 'hd'}}
hd().Release(); // expected-error{{'Release' cannot be called on the return value of 'hd'}}
hd().foo();
}
+4 -3
View File
@@ -3039,15 +3039,16 @@ public:
NS_IMETHOD SetOriginalURI(nsIURI*) NO_IMPL
NS_IMETHOD GetURI(nsIURI** aUri) override
{
NS_IF_ADDREF(mUri);
*aUri = mUri;
nsCOMPtr<nsIURI> copy = mUri;
copy.forget(aUri);
return NS_OK;
}
NS_IMETHOD GetOwner(nsISupports**) NO_IMPL
NS_IMETHOD SetOwner(nsISupports*) NO_IMPL
NS_IMETHOD GetLoadInfo(nsILoadInfo** aLoadInfo) override
{
NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
nsCOMPtr<nsILoadInfo> copy = mLoadInfo;
copy.forget(aLoadInfo);
return NS_OK;
}
NS_IMETHOD SetLoadInfo(nsILoadInfo* aLoadInfo) override
+2 -2
View File
@@ -277,8 +277,8 @@ GeckoMediaPluginService::GetThread(nsIThread** aThread)
InitializePlugins();
}
NS_ADDREF(mGMPThread);
*aThread = mGMPThread;
nsCOMPtr<nsIThread> copy = mGMPThread;
copy.forget(aThread);
return NS_OK;
}
+1 -1
View File
@@ -252,7 +252,7 @@ GetRetainedImageFromSourceSurface(SourceSurface *aSurface)
if (!data) {
MOZ_CRASH("unsupported source surface");
}
data->AddRef();
data.get()->AddRef();
return CreateCGImage(releaseDataSurface, data.get(),
data->GetData(), data->GetSize(),
data->Stride(), data->GetFormat());
+2 -2
View File
@@ -465,8 +465,8 @@ FTPChannelParent::GetInterface(const nsIID& uuid, void** result)
{
// Only support nsILoadContext if child channel's callbacks did too
if (uuid.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) {
NS_ADDREF(mLoadContext);
*result = static_cast<nsILoadContext*>(mLoadContext);
nsCOMPtr<nsILoadContext> copy = mLoadContext;
copy.forget(result);
return NS_OK;
}
+2 -2
View File
@@ -238,8 +238,8 @@ HttpChannelParent::GetInterface(const nsIID& aIID, void **result)
// Only support nsILoadContext if child channel's callbacks did too
if (aIID.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) {
NS_ADDREF(mLoadContext);
*result = static_cast<nsILoadContext*>(mLoadContext);
nsCOMPtr<nsILoadContext> copy = mLoadContext;
copy.forget(result);
return NS_OK;
}
@@ -304,8 +304,8 @@ WebSocketChannelParent::GetInterface(const nsIID & iid, void **result)
// Only support nsILoadContext if child channel's callbacks did too
if (iid.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) {
NS_ADDREF(mLoadContext);
*result = static_cast<nsILoadContext*>(mLoadContext);
nsCOMPtr<nsILoadContext> copy = mLoadContext;
copy.forget(result);
return NS_OK;
}
@@ -341,8 +341,8 @@ WyciwygChannelParent::GetInterface(const nsIID& uuid, void** result)
{
// Only support nsILoadContext if child channel's callbacks did too
if (uuid.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) {
NS_ADDREF(mLoadContext);
*result = static_cast<nsILoadContext*>(mLoadContext);
nsCOMPtr<nsILoadContext> copy = mLoadContext;
copy.forget(result);
return NS_OK;
}
+90 -2
View File
@@ -11,10 +11,12 @@
#include "mozilla/Attributes.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/Mutex.h"
#include "base/basictypes.h"
#include "nsMultiplexInputStream.h"
#include "nsICloneableInputStream.h"
#include "nsIMultiplexInputStream.h"
#include "nsISeekableStream.h"
#include "nsCOMPtr.h"
@@ -23,6 +25,7 @@
#include "nsIIPCSerializableInputStream.h"
#include "mozilla/ipc/InputStreamUtils.h"
using namespace mozilla;
using namespace mozilla::ipc;
using mozilla::DeprecatedAbs;
@@ -31,6 +34,7 @@ class nsMultiplexInputStream final
: public nsIMultiplexInputStream
, public nsISeekableStream
, public nsIIPCSerializableInputStream
, public nsICloneableInputStream
{
public:
nsMultiplexInputStream();
@@ -40,6 +44,7 @@ public:
NS_DECL_NSIMULTIPLEXINPUTSTREAM
NS_DECL_NSISEEKABLESTREAM
NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
NS_DECL_NSICLONEABLEINPUTSTREAM
private:
~nsMultiplexInputStream()
@@ -59,6 +64,7 @@ private:
const char* aFromRawSegment, uint32_t aToOffset,
uint32_t aCount, uint32_t* aWriteCount);
Mutex mLock; // Protects access to all data members.
nsTArray<nsCOMPtr<nsIInputStream>> mStreams;
uint32_t mCurrentStream;
bool mStartedReadingCurrent;
@@ -75,7 +81,8 @@ NS_IMPL_QUERY_INTERFACE_CI(nsMultiplexInputStream,
nsIMultiplexInputStream,
nsIInputStream,
nsISeekableStream,
nsIIPCSerializableInputStream)
nsIIPCSerializableInputStream,
nsICloneableInputStream)
NS_IMPL_CI_INTERFACE_GETTER(nsMultiplexInputStream,
nsIMultiplexInputStream,
nsIInputStream,
@@ -119,7 +126,8 @@ TellMaybeSeek(nsISeekableStream* aSeekable, int64_t* aResult)
}
nsMultiplexInputStream::nsMultiplexInputStream()
: mCurrentStream(0),
: mLock("nsMultiplexInputStream lock"),
mCurrentStream(0),
mStartedReadingCurrent(false),
mStatus(NS_OK)
{
@@ -129,6 +137,7 @@ nsMultiplexInputStream::nsMultiplexInputStream()
NS_IMETHODIMP
nsMultiplexInputStream::GetCount(uint32_t* aCount)
{
MutexAutoLock lock(mLock);
*aCount = mStreams.Length();
return NS_OK;
}
@@ -150,6 +159,7 @@ SeekableStreamAtBeginning(nsIInputStream* aStream)
NS_IMETHODIMP
nsMultiplexInputStream::AppendStream(nsIInputStream* aStream)
{
MutexAutoLock lock(mLock);
NS_ASSERTION(SeekableStreamAtBeginning(aStream),
"Appended stream not at beginning.");
return mStreams.AppendElement(aStream) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
@@ -159,6 +169,7 @@ nsMultiplexInputStream::AppendStream(nsIInputStream* aStream)
NS_IMETHODIMP
nsMultiplexInputStream::InsertStream(nsIInputStream* aStream, uint32_t aIndex)
{
MutexAutoLock lock(mLock);
NS_ASSERTION(SeekableStreamAtBeginning(aStream),
"Inserted stream not at beginning.");
mStreams.InsertElementAt(aIndex, aStream);
@@ -173,6 +184,7 @@ nsMultiplexInputStream::InsertStream(nsIInputStream* aStream, uint32_t aIndex)
NS_IMETHODIMP
nsMultiplexInputStream::RemoveStream(uint32_t aIndex)
{
MutexAutoLock lock(mLock);
mStreams.RemoveElementAt(aIndex);
if (mCurrentStream > aIndex) {
--mCurrentStream;
@@ -187,6 +199,7 @@ nsMultiplexInputStream::RemoveStream(uint32_t aIndex)
NS_IMETHODIMP
nsMultiplexInputStream::GetStream(uint32_t aIndex, nsIInputStream** aResult)
{
MutexAutoLock lock(mLock);
*aResult = mStreams.SafeElementAt(aIndex, nullptr);
if (NS_WARN_IF(!*aResult)) {
return NS_ERROR_NOT_AVAILABLE;
@@ -200,6 +213,7 @@ nsMultiplexInputStream::GetStream(uint32_t aIndex, nsIInputStream** aResult)
NS_IMETHODIMP
nsMultiplexInputStream::Close()
{
MutexAutoLock lock(mLock);
mStatus = NS_BASE_STREAM_CLOSED;
nsresult rv = NS_OK;
@@ -219,6 +233,7 @@ nsMultiplexInputStream::Close()
NS_IMETHODIMP
nsMultiplexInputStream::Available(uint64_t* aResult)
{
MutexAutoLock lock(mLock);
if (NS_FAILED(mStatus)) {
return mStatus;
}
@@ -243,6 +258,7 @@ nsMultiplexInputStream::Available(uint64_t* aResult)
NS_IMETHODIMP
nsMultiplexInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* aResult)
{
MutexAutoLock lock(mLock);
// It is tempting to implement this method in terms of ReadSegments, but
// that would prevent this class from being used with streams that only
// implement Read (e.g., file streams).
@@ -294,6 +310,8 @@ NS_IMETHODIMP
nsMultiplexInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
uint32_t aCount, uint32_t* aResult)
{
MutexAutoLock lock(mLock);
if (mStatus == NS_BASE_STREAM_CLOSED) {
*aResult = 0;
return NS_OK;
@@ -371,6 +389,8 @@ nsMultiplexInputStream::ReadSegCb(nsIInputStream* aIn, void* aClosure,
NS_IMETHODIMP
nsMultiplexInputStream::IsNonBlocking(bool* aNonBlocking)
{
MutexAutoLock lock(mLock);
uint32_t len = mStreams.Length();
if (len == 0) {
// Claim to be non-blocking, since we won't block the caller.
@@ -399,6 +419,8 @@ nsMultiplexInputStream::IsNonBlocking(bool* aNonBlocking)
NS_IMETHODIMP
nsMultiplexInputStream::Seek(int32_t aWhence, int64_t aOffset)
{
MutexAutoLock lock(mLock);
if (NS_FAILED(mStatus)) {
return mStatus;
}
@@ -640,6 +662,8 @@ nsMultiplexInputStream::Seek(int32_t aWhence, int64_t aOffset)
NS_IMETHODIMP
nsMultiplexInputStream::Tell(int64_t* aResult)
{
MutexAutoLock lock(mLock);
if (NS_FAILED(mStatus)) {
return mStatus;
}
@@ -697,6 +721,8 @@ void
nsMultiplexInputStream::Serialize(InputStreamParams& aParams,
FileDescriptorArray& aFileDescriptors)
{
MutexAutoLock lock(mLock);
MultiplexInputStreamParams params;
uint32_t streamCount = mStreams.Length();
@@ -757,3 +783,65 @@ nsMultiplexInputStream::Deserialize(const InputStreamParams& aParams,
return true;
}
NS_IMETHODIMP
nsMultiplexInputStream::GetCloneable(bool* aCloneable)
{
MutexAutoLock lock(mLock);
//XXXnsm Cloning a multiplex stream which has started reading is not permitted
//right now.
if (mCurrentStream > 0 || mStartedReadingCurrent) {
*aCloneable = false;
return NS_OK;
}
uint32_t len = mStreams.Length();
for (uint32_t i = 0; i < len; ++i) {
nsCOMPtr<nsICloneableInputStream> cis = do_QueryInterface(mStreams[i]);
if (!cis || !cis->GetCloneable()) {
*aCloneable = false;
return NS_OK;
}
}
*aCloneable = true;
return NS_OK;
}
NS_IMETHODIMP
nsMultiplexInputStream::Clone(nsIInputStream** aClone)
{
MutexAutoLock lock(mLock);
//XXXnsm Cloning a multiplex stream which has started reading is not permitted
//right now.
if (mCurrentStream > 0 || mStartedReadingCurrent) {
return NS_ERROR_FAILURE;
}
nsRefPtr<nsMultiplexInputStream> clone = new nsMultiplexInputStream();
nsresult rv;
uint32_t len = mStreams.Length();
for (uint32_t i = 0; i < len; ++i) {
nsCOMPtr<nsICloneableInputStream> substream = do_QueryInterface(mStreams[i]);
if (NS_WARN_IF(!substream)) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIInputStream> clonedSubstream;
rv = substream->Clone(getter_AddRefs(clonedSubstream));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = clone->AppendStream(clonedSubstream);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
clone.forget(aClone);
return NS_OK;
}
+4
View File
@@ -852,6 +852,10 @@ nsresult
NS_CloneInputStream(nsIInputStream* aSource, nsIInputStream** aCloneOut,
nsIInputStream** aReplacementOut)
{
if (NS_WARN_IF(!aSource)) {
return NS_ERROR_FAILURE;
}
// Attempt to perform the clone directly on the source stream
nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(aSource);
if (cloneable && cloneable->GetCloneable()) {
@@ -7,10 +7,19 @@
#include "Helpers.h"
#include "mozilla/unused.h"
#include "nsICloneableInputStream.h"
#include "nsIMultiplexInputStream.h"
#include "nsNetUtil.h"
#include "nsStreamUtils.h"
#include "nsStringStream.h"
TEST(CloneInputStream, InvalidInput)
{
nsCOMPtr<nsIInputStream> clone;
nsresult rv = NS_CloneInputStream(nullptr, getter_AddRefs(clone));
ASSERT_TRUE(NS_FAILED(rv));
ASSERT_FALSE(clone);
}
TEST(CloneInputStream, CloneableInput)
{
nsTArray<char> inputData;
@@ -102,3 +111,88 @@ TEST(CloneInputStream, NonCloneableInput_Fallback)
testing::ConsumeAndValidateStream(stream, inputString);
testing::ConsumeAndValidateStream(clone, inputString);
}
TEST(CloneInputStream, CloneMultiplexStream)
{
nsCOMPtr<nsIMultiplexInputStream> stream =
do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
ASSERT_TRUE(stream);
nsTArray<char> inputData;
testing::CreateData(1024, inputData);
for (uint32_t i = 0; i < 2; ++i) {
nsCString inputString(inputData.Elements(), inputData.Length());
nsCOMPtr<nsIInputStream> base;
nsresult rv = NS_NewCStringInputStream(getter_AddRefs(base), inputString);
ASSERT_TRUE(NS_SUCCEEDED(rv));
rv = stream->AppendStream(base);
ASSERT_TRUE(NS_SUCCEEDED(rv));
}
// Unread stream should clone successfully.
nsTArray<char> doubled;
doubled.AppendElements(inputData);
doubled.AppendElements(inputData);
nsCOMPtr<nsIInputStream> clone;
nsresult rv = NS_CloneInputStream(stream, getter_AddRefs(clone));
ASSERT_TRUE(NS_SUCCEEDED(rv));
testing::ConsumeAndValidateStream(clone, doubled);
// Stream that has been read should fail.
nsAutoPtr<char> buffer(new char[512]);
uint32_t read;
rv = stream->Read(buffer, 512, &read);
ASSERT_TRUE(NS_SUCCEEDED(rv));
nsCOMPtr<nsIInputStream> clone2;
rv = NS_CloneInputStream(stream, getter_AddRefs(clone2));
ASSERT_TRUE(NS_FAILED(rv));
}
TEST(CloneInputStream, CloneMultiplexStreamPartial)
{
nsCOMPtr<nsIMultiplexInputStream> stream =
do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
ASSERT_TRUE(stream);
nsTArray<char> inputData;
testing::CreateData(1024, inputData);
for (uint32_t i = 0; i < 2; ++i) {
nsCString inputString(inputData.Elements(), inputData.Length());
nsCOMPtr<nsIInputStream> base;
nsresult rv = NS_NewCStringInputStream(getter_AddRefs(base), inputString);
ASSERT_TRUE(NS_SUCCEEDED(rv));
rv = stream->AppendStream(base);
ASSERT_TRUE(NS_SUCCEEDED(rv));
}
// Fail when first stream read, but second hasn't been started.
nsAutoPtr<char> buffer(new char[1024]);
uint32_t read;
nsresult rv = stream->Read(buffer, 1024, &read);
ASSERT_TRUE(NS_SUCCEEDED(rv));
nsCOMPtr<nsIInputStream> clone;
rv = NS_CloneInputStream(stream, getter_AddRefs(clone));
ASSERT_TRUE(NS_FAILED(rv));
// Fail after beginning read of second stream.
rv = stream->Read(buffer, 512, &read);
ASSERT_TRUE(NS_SUCCEEDED(rv) && read == 512);
rv = NS_CloneInputStream(stream, getter_AddRefs(clone));
ASSERT_TRUE(NS_FAILED(rv));
// Fail at the end.
nsAutoCString consumed;
rv = NS_ConsumeStream(stream, UINT32_MAX, consumed);
ASSERT_TRUE(NS_SUCCEEDED(rv));
rv = NS_CloneInputStream(stream, getter_AddRefs(clone));
ASSERT_TRUE(NS_FAILED(rv));
}