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_;
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");
}
/// 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
/// \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);
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());
+}
+
}