BIND 10 trac2375, updated. ef8559b5d0b9c87852f1bc451c80b7aed97f66df [2375] Provide weak exception guarantee
BIND 10 source code commits
bind10-changes at lists.isc.org
Wed Nov 14 13:23:45 UTC 2012
The branch, trac2375 has been updated
via ef8559b5d0b9c87852f1bc451c80b7aed97f66df (commit)
from a8f348a0fbcf9d95e0bc2f27ef953b4ea246c2cc (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit ef8559b5d0b9c87852f1bc451c80b7aed97f66df
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Wed Nov 14 13:59:15 2012 +0100
[2375] Provide weak exception guarantee
-----------------------------------------------------------------------
Summary of changes:
src/lib/dns/master_lexer.cc | 33 +++++++++++++++++--------
src/lib/dns/master_lexer.h | 9 +++++++
src/lib/dns/tests/master_lexer_unittest.cc | 36 ++++++++++++++++++++++++++++
3 files changed, 68 insertions(+), 10 deletions(-)
-----------------------------------------------------------------------
diff --git a/src/lib/dns/master_lexer.cc b/src/lib/dns/master_lexer.cc
index 5922758..fc09f7b 100644
--- a/src/lib/dns/master_lexer.cc
+++ b/src/lib/dns/master_lexer.cc
@@ -143,6 +143,9 @@ MasterLexer::getNextToken(Options options) {
isc_throw(isc::InvalidOperation, "No source to read tokens from");
}
// Store current state, for the case we need to restore by ungetToken
+ impl_->has_previous_ = false;
+ // If thrown in this section, nothing observable except for not having
+ // previous will happen.
impl_->previous_token_ = impl_->token_;
impl_->previous_paren_count_ = impl_->paren_count_;
impl_->previous_was_eol_ = impl_->last_was_eol_;
@@ -150,26 +153,36 @@ MasterLexer::getNextToken(Options options) {
impl_->has_previous_ = true;
// Reset the token now. This is to check a token was actually produced.
// This is debugging aid.
- impl_->token_ = Token(Token::NO_TOKEN_PRODUCED);
- for (const State *state = start(options); state != NULL;
- state = state->handle(*this)) {
- // Do nothing here. All is handled in the for cycle header itself.
+
+ // From now on, if we throw, we can at least restore the old state.
+ try {
+ impl_->token_ = Token(Token::NO_TOKEN_PRODUCED);
+ for (const State *state = start(options); state != NULL;
+ state = state->handle(*this)) {
+ // Do nothing here. All is handled in the for cycle header itself.
+ }
+ // Make sure a token was produced. Since this Can Not Happen, we assert
+ // here instead of throwing.
+ assert(impl_->token_.getType() != Token::ERROR ||
+ impl_->token_.getErrorCode() != Token::NO_TOKEN_PRODUCED);
+ return (impl_->token_);
+ } catch (...) {
+ // Restore the old state and let the exception continue.
+ ungetToken();
+ throw;
}
- // Make sure a token was produced. Since this Can Not Happen, we assert
- // here instead of throwing.
- assert(impl_->token_.getType() != Token::ERROR ||
- impl_->token_.getErrorCode() != Token::NO_TOKEN_PRODUCED);
- return (impl_->token_);
}
void
MasterLexer::ungetToken() {
if (impl_->has_previous_) {
+ // The one that could fail due to bad_alloc first
+ impl_->token_ = impl_->previous_token_;
+ // The rest should not throw.
impl_->has_previous_ = false;
impl_->source_->ungetAll();
impl_->last_was_eol_ = impl_->previous_was_eol_;
impl_->paren_count_ = impl_->previous_paren_count_;
- impl_->token_ = impl_->previous_token_;
} else {
isc_throw(isc::InvalidOperation, "No token to unget ready");
}
diff --git a/src/lib/dns/master_lexer.h b/src/lib/dns/master_lexer.h
index f46830b..dee6197 100644
--- a/src/lib/dns/master_lexer.h
+++ b/src/lib/dns/master_lexer.h
@@ -183,6 +183,12 @@ public:
/// It reads a bit of the last opened source and produces another token
/// found in it.
///
+ /// This method provides the weak exception guarantee. It'll return the
+ /// class to the state before the call on exception, except that it is
+ /// not possible to call ungetToken() after the failed call. This is
+ /// considered OK, since the caller was expecting the call to succeed
+ /// in which case the previous token would not be ungettable as well.
+ ///
/// \param options The options can be used to modify the tokenization.
/// The method can be made reporting things which are usually ignored
/// by this parameter. Multiple options can be passed at once by
@@ -192,6 +198,9 @@ public:
/// \throw isc::InvalidOperation in case the source is not available. This
/// may mean the pushSource() has not been called yet, or that the
/// current source has been read past the end.
+ /// \throw isc::master_lexer_internal::InputSource::ReadError in case
+ /// there's problem reading from the underlying source (eg. I/O error
+ /// in the file on the disk).
/// \throw std::bad_alloc in case allocation of some internal resources
/// or the token fail.
Token getNextToken(Options options = NONE);
diff --git a/src/lib/dns/tests/master_lexer_unittest.cc b/src/lib/dns/tests/master_lexer_unittest.cc
index 2e98cab..b02ae70 100644
--- a/src/lib/dns/tests/master_lexer_unittest.cc
+++ b/src/lib/dns/tests/master_lexer_unittest.cc
@@ -384,4 +384,40 @@ TEST_F(MasterLexerTest, ungetAfterSwitch) {
EXPECT_THROW(lexer.ungetToken(), isc::InvalidOperation);
}
+class TestException {};
+
+void
+doThrow(const std::string&) {
+ throw TestException();
+}
+
+// Check the getNextToken provides at least the weak exception guarantee.
+TEST_F(MasterLexerTest, getTokenExceptions) {
+ ss << "\n12345";
+ lexer.pushSource(ss);
+
+ // Prepare a chain that changes the internal state, reads something.
+ // The next item in the chain will throw an exception (we explicitly
+ // throw something not known to it, so we know it can handle anything).
+ // Then the thing should get to the previous state and getting the
+ // token the usual way without mock should work.
+ const bool true_value = true;
+ boost::scoped_ptr<State> s2(State::getFakeState(NULL, 3, NULL, 0, NULL,
+ &doThrow));
+ boost::scoped_ptr<State> s1(State::getFakeState(s2.get(), 3, NULL, 1,
+ &true_value));
+ lexer.pushFakeStart(s1.get());
+
+ // Getting the token with the fake start should throw. But then, the
+ // current state should be untouched.
+ EXPECT_THROW(lexer.getNextToken(), TestException);
+ EXPECT_EQ(0, s1->getParenCount(lexer));
+ EXPECT_FALSE(s1->wasLastEOL(lexer));
+ EXPECT_EQ(MasterLexer::Token::NOT_STARTED,
+ s1->getToken(lexer).getErrorCode());
+
+ // It gets back to the original state, so getting the newline works.
+ EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
+}
+
}
More information about the bind10-changes
mailing list