Skip to content

Commit 21dd66a

Browse files
authored
Avoid throwing exceptions in HashValidator. (#1753)
In general, we prefer not to throw exceptions to signal an hash mismatch error. This makes the code easier to use if the application has disabled exceptions. There is one case where we must throw an exception though: if an error is detected in the ObjectReadStreambuf the only mechanism to signal error is exceptions (these classes must obey the protocol defined by the standard library, and the library is designed with exceptions in mind). When exceptions are disabled and we detect such errors we set the `status()`, and the application is expected to check it. This is not ideal, but the best that can be done in that case.
1 parent 1dc9be1 commit 21dd66a

11 files changed

+48
-184
lines changed

google/cloud/storage/client_write_object_test.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class MockStreambuf : public internal::ObjectWriteStreambuf {
6060
public:
6161
MOCK_CONST_METHOD0(IsOpen, bool());
6262
MOCK_METHOD0(DoClose, StatusOr<internal::HttpResponse>());
63-
MOCK_METHOD1(ValidateHash, void(ObjectMetadata const&));
63+
MOCK_METHOD1(ValidateHash, bool(ObjectMetadata const&));
6464
MOCK_CONST_METHOD0(received_hash, std::string const&());
6565
MOCK_CONST_METHOD0(computed_hash, std::string const&());
6666
MOCK_CONST_METHOD0(resumable_session_id, std::string const&());

google/cloud/storage/internal/curl_resumable_streambuf.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ bool CurlResumableStreambuf::IsOpen() const {
3535
return static_cast<bool>(upload_session_);
3636
}
3737

38-
void CurlResumableStreambuf::ValidateHash(ObjectMetadata const& meta) {
38+
bool CurlResumableStreambuf::ValidateHash(ObjectMetadata const& meta) {
3939
hash_validator_->ProcessMetadata(meta);
40-
hash_validator_result_ =
41-
HashValidator::FinishAndCheck(__func__, std::move(*hash_validator_));
40+
hash_validator_result_ = std::move(*hash_validator_).Finish();
41+
return not hash_validator_result_.is_mismatch;
4242
}
4343

4444
CurlResumableStreambuf::int_type CurlResumableStreambuf::overflow(int_type ch) {

google/cloud/storage/internal/curl_resumable_streambuf.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class CurlResumableStreambuf : public ObjectWriteStreambuf {
3939
~CurlResumableStreambuf() override = default;
4040

4141
bool IsOpen() const override;
42-
void ValidateHash(ObjectMetadata const& meta) override;
42+
bool ValidateHash(ObjectMetadata const& meta) override;
4343
std::string const& received_hash() const override {
4444
return hash_validator_result_.received;
4545
}

google/cloud/storage/internal/curl_streambuf.cc

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,34 @@ void CurlReadStreambuf::Close() {
4444
}
4545

4646
CurlReadStreambuf::int_type CurlReadStreambuf::underflow() {
47+
char const* function_name = __func__;
48+
auto report_hash_mismatch = [this,
49+
function_name]() -> CurlReadStreambuf::int_type {
50+
std::string msg;
51+
msg += function_name;
52+
msg += "() - mismatched hashes in download";
53+
#if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
54+
throw HashMismatchError(msg, hash_validator_result_.received,
55+
hash_validator_result_.computed);
56+
#else
57+
msg += ", expected=";
58+
msg += hash_validator_result_.computed;
59+
msg += ", received=";
60+
msg += hash_validator_result_.received;
61+
status_ = Status(StatusCode::DATA_LOSS, std::move(msg));
62+
return traits_type::eof();
63+
#endif // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
64+
};
65+
4766
if (not IsOpen()) {
4867
// The stream is closed, reading from a closed stream can happen if there is
4968
// no object to read from, or the object is empty. In that case just setup
5069
// an empty (but valid) region and verify the checksums.
5170
SetEmptyRegion();
52-
hash_validator_result_ = HashValidator::FinishAndCheck(
53-
__func__ + std::string(" mismatched hashes reading from closed stream"),
54-
std::move(*hash_validator_));
71+
hash_validator_result_ = std::move(*hash_validator_).Finish();
72+
if (hash_validator_result_.is_mismatch) {
73+
return report_hash_mismatch();
74+
}
5575
return traits_type::eof();
5676
}
5777

@@ -80,9 +100,10 @@ CurlReadStreambuf::int_type CurlReadStreambuf::underflow() {
80100
// empty (but valid) region:
81101
SetEmptyRegion();
82102
// Verify the checksums, and return the EOF character.
83-
hash_validator_result_ = HashValidator::FinishAndCheck(
84-
__func__ + std::string(" mismatched hashes at end of download"),
85-
std::move(*hash_validator_));
103+
hash_validator_result_ = std::move(*hash_validator_).Finish();
104+
if (hash_validator_result_.is_mismatch) {
105+
return report_hash_mismatch();
106+
}
86107
return traits_type::eof();
87108
}
88109

@@ -121,10 +142,10 @@ CurlWriteStreambuf::CurlWriteStreambuf(
121142

122143
bool CurlWriteStreambuf::IsOpen() const { return upload_.IsOpen(); }
123144

124-
void CurlWriteStreambuf::ValidateHash(ObjectMetadata const& meta) {
145+
bool CurlWriteStreambuf::ValidateHash(ObjectMetadata const& meta) {
125146
hash_validator_->ProcessMetadata(meta);
126-
hash_validator_result_ =
127-
HashValidator::FinishAndCheck(__func__, std::move(*hash_validator_));
147+
hash_validator_result_ = std::move(*hash_validator_).Finish();
148+
return not hash_validator_result_.is_mismatch;
128149
}
129150

130151
CurlWriteStreambuf::int_type CurlWriteStreambuf::overflow(int_type ch) {

google/cloud/storage/internal/curl_streambuf.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ class CurlWriteStreambuf : public ObjectWriteStreambuf {
7979
~CurlWriteStreambuf() override = default;
8080

8181
bool IsOpen() const override;
82-
void ValidateHash(ObjectMetadata const& meta) override;
82+
bool ValidateHash(ObjectMetadata const& meta) override;
8383
std::string const& received_hash() const override {
8484
return hash_validator_result_.received;
8585
}

google/cloud/storage/internal/hash_validator.cc

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,32 +26,6 @@ namespace storage {
2626
inline namespace STORAGE_CLIENT_NS {
2727
namespace internal {
2828

29-
void HashValidator::CheckResult(std::string const& msg,
30-
HashValidator::Result const& result) {
31-
if (not result.is_mismatch) {
32-
return;
33-
}
34-
#if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
35-
// Sometimes the server simply does not have a MD5 hash to send us, the most
36-
// common case is a composed object, particularly one formed from encrypted
37-
// components, where computing the MD5 would require decrypting and re-reading
38-
// all the components. In that case we just do not raise an exception even if
39-
// there is a mismatch.
40-
throw HashMismatchError(msg, result.received, result.computed);
41-
#else
42-
GCP_LOG(ERROR) << "Mismatched hashes: "
43-
<< HashMismatchError(
44-
msg, result.received, result.computed).what();
45-
#endif // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
46-
}
47-
48-
HashValidator::Result HashValidator::FinishAndCheck(std::string const& msg,
49-
HashValidator&& validator) {
50-
auto result = std::move(validator).Finish();
51-
CheckResult(msg, result);
52-
return result;
53-
}
54-
5529
void CompositeValidator::Update(std::string const& payload) {
5630
left_->Update(payload);
5731
right_->Update(payload);

google/cloud/storage/internal/hash_validator.h

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,19 +65,6 @@ class HashValidator {
6565
* validators that disable validation.
6666
*/
6767
virtual Result Finish() && = 0;
68-
69-
/**
70-
* Raise an exception if the hashes do not match.
71-
*
72-
* @throws google::cloud::storage::HashValidation
73-
*/
74-
static void CheckResult(std::string const& msg, Result const& result);
75-
76-
/**
77-
* Call Finish() on the validator and check the result.
78-
*/
79-
static Result FinishAndCheck(std::string const& msg,
80-
HashValidator&& validator);
8168
};
8269

8370
/**

google/cloud/storage/internal/hash_validator_test.cc

Lines changed: 0 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -36,79 +36,6 @@ const std::string EMPTY_STRING_MD5_HASH = "1B2M2Y8AsgTpgAmY7PhCfg==";
3636
const std::string QUICK_FOX_CRC32C_CHECKSUM = "ImIEBA==";
3737
const std::string QUICK_FOX_MD5_HASH = "nhB9nTcrtoJr2B01QqQZ1g==";
3838

39-
TEST(HashValidator, CheckResultMismatch) {
40-
HashValidator::Result result{"received-hash", "computed-hash", true};
41-
#if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
42-
EXPECT_THROW(try { HashValidator::CheckResult("test-msg", result); } catch (
43-
google::cloud::storage::HashMismatchError const& ex) {
44-
EXPECT_EQ("received-hash", ex.received_hash());
45-
EXPECT_EQ("computed-hash", ex.computed_hash());
46-
EXPECT_THAT(ex.what(), HasSubstr("test-msg"));
47-
throw;
48-
},
49-
std::ios::failure);
50-
#else
51-
// The program should not be terminated in this case.
52-
HashValidator::CheckResult("test-msg", result);
53-
SUCCEED();
54-
#endif // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
55-
}
56-
57-
TEST(HashValidator, CheckResultMatch) {
58-
HashValidator::Result result{"received-hash", "computed-hash", false};
59-
#if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
60-
EXPECT_NO_THROW(HashValidator::CheckResult("test-msg", result));
61-
#else
62-
// The program should not be terminated in this case.
63-
HashValidator::CheckResult("test-msg", result);
64-
SUCCEED();
65-
#endif // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
66-
}
67-
68-
TEST(HashValidator, CheckResultEmptyReceived) {
69-
// Sometimes the service does not send the hashes back, we do not treat that
70-
// as a hash mismatch.
71-
HashValidator::Result result{"", "test-hash"};
72-
#if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
73-
EXPECT_NO_THROW(HashValidator::CheckResult("test-msg", result));
74-
#else
75-
// The program should not be terminated in this case.
76-
HashValidator::CheckResult("test-msg", result);
77-
SUCCEED();
78-
#endif // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
79-
}
80-
81-
TEST(HashValidator, FinishAndCheck) {
82-
NullHashValidator validator;
83-
validator.Update("");
84-
validator.ProcessHeader("x-goog-hash", "md5=" + EMPTY_STRING_MD5_HASH);
85-
auto result = HashValidator::FinishAndCheck("test-msg", std::move(validator));
86-
EXPECT_TRUE(result.received.empty());
87-
EXPECT_TRUE(result.computed.empty());
88-
}
89-
90-
TEST(HashValidator, FinishAndCheckMismatch) {
91-
MD5HashValidator validator;
92-
validator.Update("The quick brown fox jumps over the lazy dog");
93-
validator.ProcessHeader("x-goog-hash", "md5=" + EMPTY_STRING_MD5_HASH);
94-
#if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
95-
EXPECT_THROW(
96-
try {
97-
HashValidator::FinishAndCheck("test-msg", std::move(validator));
98-
} catch (google::cloud::storage::HashMismatchError const& ex) {
99-
EXPECT_EQ(EMPTY_STRING_MD5_HASH, ex.received_hash());
100-
EXPECT_EQ(QUICK_FOX_MD5_HASH, ex.computed_hash());
101-
EXPECT_THAT(ex.what(), HasSubstr("test-msg"));
102-
throw;
103-
},
104-
std::ios::failure);
105-
#else
106-
auto result = HashValidator::FinishAndCheck("test-msg", std::move(validator));
107-
EXPECT_EQ(EMPTY_STRING_MD5_HASH, result.received);
108-
EXPECT_EQ(QUICK_FOX_MD5_HASH, result.computed);
109-
#endif // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
110-
}
111-
11239
TEST(NullHashValidatorTest, Simple) {
11340
NullHashValidator validator;
11441
validator.ProcessHeader("x-goog-hash", "md5=<placeholder-for-test>");

google/cloud/storage/internal/object_streambuf.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class ObjectWriteStreambuf : public std::basic_streambuf<char> {
6969

7070
StatusOr<HttpResponse> Close();
7171
virtual bool IsOpen() const = 0;
72-
virtual void ValidateHash(ObjectMetadata const& meta) = 0;
72+
virtual bool ValidateHash(ObjectMetadata const& meta) = 0;
7373
virtual std::string const& received_hash() const = 0;
7474
virtual std::string const& computed_hash() const = 0;
7575

google/cloud/storage/object_stream.cc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,9 @@ void ObjectWriteStream::Close() {
9494
}
9595
}
9696

97-
// TODO(#1747) - do not throw exceptions here.
98-
buf_->ValidateHash(*metadata_);
97+
if (not buf_->ValidateHash(*metadata_)) {
98+
setstate(std::ios_base::badbit);
99+
}
99100
}
100101

101102
void ObjectWriteStream::Suspend() && { buf_.reset(); }

0 commit comments

Comments
 (0)