Bug 1210291 - Streamline StreamingLexer's handling of terminal states. r=seth.
authorNicholas Nethercote <nnethercote@mozilla.com>
Wed, 28 Oct 2015 01:30:20 -0700
changeset 272911 793133ff5233a8e2cd0a6864b9984f5a9bb31c05
parent 272910 21605be9a2c7a5a9c526e79afc062caf3d7725a1
child 272912 86f7c45c29716cd326643a30a9e48915e0e694cc
push id29688
push userkwierso@gmail.com
push dateTue, 17 Nov 2015 21:10:09 +0000
treeherdermozilla-central@eed903a7e4e7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersseth
bugs1210291
milestone45.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1210291 - Streamline StreamingLexer's handling of terminal states. r=seth. This patch introduces TerminalState and changes LexerTransition::mNextState to be a Variant<State, TerminalState>. This means that SUCCESS and FAILURE no longer need to be part of State. Some things to note: - This simplifies the handling of Lex()'s return value, which is nice. - The patch splits Terminate() into TerminateSuccess() and TerminateFailure(). - |const State& aNextState| wouldn't work for the first arg to LexerTransition's ctor due to errors in Variant construction that I didn't understand. I had to change it to |State aNextState|.
image/StreamingLexer.h
image/decoders/nsBMPDecoder.cpp
image/decoders/nsBMPDecoder.h
image/decoders/nsICODecoder.cpp
image/decoders/nsICODecoder.h
image/decoders/nsIconDecoder.cpp
image/decoders/nsIconDecoder.h
image/test/gtest/TestStreamingLexer.cpp
--- a/image/StreamingLexer.h
+++ b/image/StreamingLexer.h
@@ -1,92 +1,138 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /**
  * StreamingLexer is a lexing framework designed to make it simple to write
  * image decoders without worrying about the details of how the data is arriving
  * from the network.
  */
 
 #ifndef mozilla_image_StreamingLexer_h
 #define mozilla_image_StreamingLexer_h
 
 #include <algorithm>
 #include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
 #include "mozilla/Maybe.h"
+#include "mozilla/Variant.h"
 #include "mozilla/Vector.h"
 
 namespace mozilla {
 namespace image {
 
 /// Buffering behaviors for StreamingLexer transitions.
 enum class BufferingStrategy
 {
   BUFFERED,   // Data will be buffered and processed in one chunk.
   UNBUFFERED  // Data will be processed as it arrives, in multiple chunks.
 };
 
-/// @return true if @aState is a terminal state.
-template <typename State>
-bool IsTerminalState(State aState)
+/// The result of a call to StreamingLexer::Lex().
+enum class TerminalState
 {
-  return aState == State::SUCCESS ||
-         aState == State::FAILURE;
-}
+  SUCCESS,
+  FAILURE
+};
 
 /**
  * LexerTransition is a type used to give commands to the lexing framework.
  * Code that uses StreamingLexer can create LexerTransition values using the
  * static methods on Transition, and then return them to the lexing framework
  * for execution.
  */
 template <typename State>
 class LexerTransition
 {
 public:
-  State NextState() const { return mNextState; }
-  State UnbufferedState() const { return *mUnbufferedState; }
-  size_t Size() const { return mSize; }
-  BufferingStrategy Buffering() const { return mBufferingStrategy; }
+  // This is implicit so that Terminate{Success,Failure}() can return a
+  // TerminalState and have it implicitly converted to a
+  // LexerTransition<State>, which avoids the need for a "<State>"
+  // qualification to the Terminate{Success,Failure}() callsite.
+  MOZ_IMPLICIT LexerTransition(TerminalState aFinalState)
+    : mNextState(aFinalState)
+  {}
+
+  bool NextStateIsTerminal() const
+  {
+    return mNextState.template is<TerminalState>();
+  }
+
+  TerminalState NextStateAsTerminal() const
+  {
+    return mNextState.template as<TerminalState>();
+  }
+
+  State NextState() const
+  {
+    return mNextState.template as<NonTerminalState>().mState;
+  }
+
+  State UnbufferedState() const
+  {
+    return *mNextState.template as<NonTerminalState>().mUnbufferedState;
+  }
+
+  size_t Size() const
+  {
+    return mNextState.template as<NonTerminalState>().mSize;
+  }
+
+  BufferingStrategy Buffering() const
+  {
+    return mNextState.template as<NonTerminalState>().mBufferingStrategy;
+  }
 
 private:
   friend struct Transition;
 
-  LexerTransition(const State& aNextState,
+  LexerTransition(State aNextState,
                   const Maybe<State>& aUnbufferedState,
                   size_t aSize,
                   BufferingStrategy aBufferingStrategy)
-    : mNextState(aNextState)
-    , mUnbufferedState(aUnbufferedState)
-    , mSize(aSize)
-    , mBufferingStrategy(aBufferingStrategy)
+    : mNextState(NonTerminalState(aNextState, aUnbufferedState, aSize,
+                                  aBufferingStrategy))
+  {}
+
+  struct NonTerminalState
   {
-    MOZ_ASSERT_IF(mBufferingStrategy == BufferingStrategy::UNBUFFERED,
-                  mUnbufferedState);
-    MOZ_ASSERT_IF(mUnbufferedState,
-                  mBufferingStrategy == BufferingStrategy::UNBUFFERED);
-  }
+    State mState;
+    Maybe<State> mUnbufferedState;
+    size_t mSize;
+    BufferingStrategy mBufferingStrategy;
 
-  State mNextState;
-  Maybe<State> mUnbufferedState;
-  size_t mSize;
-  BufferingStrategy mBufferingStrategy;
+    NonTerminalState(State aState,
+                     const Maybe<State>& aUnbufferedState,
+                     size_t aSize,
+                     BufferingStrategy aBufferingStrategy)
+      : mState(aState)
+      , mUnbufferedState(aUnbufferedState)
+      , mSize(aSize)
+      , mBufferingStrategy(aBufferingStrategy)
+    {
+      MOZ_ASSERT_IF(mBufferingStrategy == BufferingStrategy::UNBUFFERED,
+                    mUnbufferedState);
+      MOZ_ASSERT_IF(mUnbufferedState,
+                    mBufferingStrategy == BufferingStrategy::UNBUFFERED);
+    }
+  };
+  Variant<NonTerminalState, TerminalState> mNextState;
 };
 
 struct Transition
 {
   /// Transition to @aNextState, buffering @aSize bytes of data.
   template <typename State>
   static LexerTransition<State>
   To(const State& aNextState, size_t aSize)
   {
-    MOZ_ASSERT(!IsTerminalState(aNextState));
     return LexerTransition<State>(aNextState, Nothing(), aSize,
                                   BufferingStrategy::BUFFERED);
   }
 
   /**
    * Transition to @aNextState via @aUnbufferedState, reading @aSize bytes of
    * data unbuffered.
    *
@@ -97,83 +143,92 @@ struct Transition
    * @aNextState will always be reached unless lexing terminates early.
    */
   template <typename State>
   static LexerTransition<State>
   ToUnbuffered(const State& aNextState,
                const State& aUnbufferedState,
                size_t aSize)
   {
-    MOZ_ASSERT(!IsTerminalState(aNextState));
-    MOZ_ASSERT(!IsTerminalState(aUnbufferedState));
     return LexerTransition<State>(aNextState, Some(aUnbufferedState), aSize,
                                   BufferingStrategy::UNBUFFERED);
   }
 
   /**
    * Continue receiving unbuffered data. @aUnbufferedState should be the same
    * state as the @aUnbufferedState specified in the preceding call to
    * ToUnbuffered().
    *
    * This should be used during an unbuffered read initiated by ToUnbuffered().
    */
   template <typename State>
   static LexerTransition<State>
   ContinueUnbuffered(const State& aUnbufferedState)
   {
-    MOZ_ASSERT(!IsTerminalState(aUnbufferedState));
     return LexerTransition<State>(aUnbufferedState, Nothing(), 0,
                                   BufferingStrategy::BUFFERED);
   }
 
   /**
-   * Terminate lexing, ending up in terminal state @aFinalState.
+   * Terminate lexing, ending up in terminal state SUCCESS. (The implicit
+   * LexerTransition constructor will convert the result to a LexerTransition
+   * as needed.)
    *
-   * No more data will be delivered after Terminate() is used.
+   * No more data will be delivered after this function is used.
    */
-  template <typename State>
-  static LexerTransition<State>
-  Terminate(const State& aFinalState)
+  static TerminalState
+  TerminateSuccess()
   {
-    MOZ_ASSERT(IsTerminalState(aFinalState));
-    return LexerTransition<State>(aFinalState, Nothing(), 0,
-                                  BufferingStrategy::BUFFERED);
+    return TerminalState::SUCCESS;
+  }
+
+  /**
+   * Terminate lexing, ending up in terminal state FAILURE. (The implicit
+   * LexerTransition constructor will convert the result to a LexerTransition
+   * as needed.)
+   *
+   * No more data will be delivered after this function is used.
+   */
+  static TerminalState
+  TerminateFailure()
+  {
+    return TerminalState::FAILURE;
   }
 
 private:
   Transition();
 };
 
 /**
  * StreamingLexer is a lexing framework designed to make it simple to write
  * image decoders without worrying about the details of how the data is arriving
  * from the network.
  *
  * To use StreamingLexer:
  *
  *  - Create a State type. This should be an |enum class| listing all of the
  *    states that you can be in while lexing the image format you're trying to
- *    read. It must contain the two terminal states SUCCESS and FAILURE.
+ *    read.
  *
  *  - Add an instance of StreamingLexer<State> to your decoder class. Initialize
  *    it with a Transition::To() the state that you want to start lexing in.
  *
  *  - In your decoder's WriteInternal method(), call Lex(), passing in the input
  *    data and length that are passed to WriteInternal(). You also need to pass
  *    a lambda which dispatches to lexing code for each state based on the State
  *    value that's passed in. The lambda generally should just continue a
  *    |switch| statement that calls different methods for each State value. Each
  *    method should return a LexerTransition<State>, which the lambda should
  *    return in turn.
  *
  *  - Write the methods that actually implement lexing for your image format.
  *    These methods should return either Transition::To(), to move on to another
- *    state, or Transition::Terminate(), if lexing has terminated in either
- *    success or failure. (There are also additional transitions for unbuffered
- *    reads; see below.)
+ *    state, or Transition::Terminate{Success,Failure}(), if lexing has
+ *    terminated in either success or failure. (There are also additional
+ *    transitions for unbuffered reads; see below.)
  *
  * That's all there is to it. The StreamingLexer will track your position in the
  * input and buffer enough data so that your lexing methods can process
  * everything in one pass. Lex() returns Nothing() if more data is needed, in
  * which case you should just return from WriteInternal(). If lexing reaches a
  * terminal state, Lex() returns Some(State::SUCCESS) or Some(State::FAILURE),
  * and you can check which one to determine if lexing succeeded or failed and do
  * any necessary cleanup.
@@ -203,57 +258,57 @@ class StreamingLexer
 {
 public:
   explicit StreamingLexer(LexerTransition<State> aStartState)
     : mTransition(aStartState)
     , mToReadUnbuffered(0)
   { }
 
   template <typename Func>
-  Maybe<State> Lex(const char* aInput, size_t aLength, Func aFunc)
+  Maybe<TerminalState> Lex(const char* aInput, size_t aLength, Func aFunc)
   {
-    if (IsTerminalState(mTransition.NextState())) {
+    if (mTransition.NextStateIsTerminal()) {
       // We've already reached a terminal state. We never deliver any more data
       // in this case; just return the terminal state again immediately.
-      return Some(mTransition.NextState());
+      return Some(mTransition.NextStateAsTerminal());
     }
 
     if (mToReadUnbuffered > 0) {
       // We're continuing an unbuffered read.
 
       MOZ_ASSERT(mBuffer.empty(),
                  "Shouldn't be continuing an unbuffered read and a buffered "
                  "read at the same time");
 
       size_t toRead = std::min(mToReadUnbuffered, aLength);
 
-      // Call aFunc with the unbuffered state to indicate that we're in the middle
-      // of an unbuffered read. We enforce that any state transition passed back
-      // to us is either a terminal states or takes us back to the unbuffered
-      // state.
+      // Call aFunc with the unbuffered state to indicate that we're in the
+      // middle of an unbuffered read. We enforce that any state transition
+      // passed back to us is either a terminal state or takes us back to the
+      // unbuffered state.
       LexerTransition<State> unbufferedTransition =
         aFunc(mTransition.UnbufferedState(), aInput, toRead);
-      if (IsTerminalState(unbufferedTransition.NextState())) {
+      if (unbufferedTransition.NextStateIsTerminal()) {
         mTransition = unbufferedTransition;
-        return Some(mTransition.NextState());  // Done!
+        return Some(mTransition.NextStateAsTerminal());  // Done!
       }
       MOZ_ASSERT(mTransition.UnbufferedState() ==
                    unbufferedTransition.NextState());
 
       aInput += toRead;
       aLength -= toRead;
       mToReadUnbuffered -= toRead;
       if (mToReadUnbuffered != 0) {
         return Nothing();  // Need more input.
       }
 
       // We're done with the unbuffered read, so transition to the next state.
       mTransition = aFunc(mTransition.NextState(), nullptr, 0);
-      if (IsTerminalState(mTransition.NextState())) {
-        return Some(mTransition.NextState());  // Done!
+      if (mTransition.NextStateIsTerminal()) {
+        return Some(mTransition.NextStateAsTerminal());  // Done!
       }
     } else if (0 < mBuffer.length()) {
       // We're continuing a buffered read.
 
       MOZ_ASSERT(mToReadUnbuffered == 0,
                  "Shouldn't be continuing an unbuffered read and a buffered "
                  "read at the same time");
       MOZ_ASSERT(mBuffer.length() < mTransition.Size(),
@@ -267,18 +322,18 @@ public:
       if (mBuffer.length() != mTransition.Size()) {
         return Nothing();  // Need more input.
       }
 
       // We've buffered everything, so transition to the next state.
       mTransition =
         aFunc(mTransition.NextState(), mBuffer.begin(), mBuffer.length());
       mBuffer.clear();
-      if (IsTerminalState(mTransition.NextState())) {
-        return Some(mTransition.NextState());  // Done!
+      if (mTransition.NextStateIsTerminal()) {
+        return Some(mTransition.NextStateAsTerminal());  // Done!
       }
     }
 
     MOZ_ASSERT(mToReadUnbuffered == 0);
     MOZ_ASSERT(mBuffer.empty());
 
     // Process states as long as we continue to have enough input to do so.
     while (mTransition.Size() <= aLength) {
@@ -286,63 +341,63 @@ public:
 
       if (mTransition.Buffering() == BufferingStrategy::BUFFERED) {
         mTransition = aFunc(mTransition.NextState(), aInput, toRead);
       } else {
         MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::UNBUFFERED);
 
         // Call aFunc with the unbuffered state to indicate that we're in the
         // middle of an unbuffered read. We enforce that any state transition
-        // passed back to us is either a terminal states or takes us back to the
+        // passed back to us is either a terminal state or takes us back to the
         // unbuffered state.
         LexerTransition<State> unbufferedTransition =
           aFunc(mTransition.UnbufferedState(), aInput, toRead);
-        if (IsTerminalState(unbufferedTransition.NextState())) {
+        if (unbufferedTransition.NextStateIsTerminal()) {
           mTransition = unbufferedTransition;
-          return Some(mTransition.NextState());  // Done!
+          return Some(mTransition.NextStateAsTerminal());  // Done!
         }
         MOZ_ASSERT(mTransition.UnbufferedState() ==
                      unbufferedTransition.NextState());
 
         // We're done with the unbuffered read, so transition to the next state.
         mTransition = aFunc(mTransition.NextState(), nullptr, 0);
       }
 
       aInput += toRead;
       aLength -= toRead;
 
-      if (IsTerminalState(mTransition.NextState())) {
-        return Some(mTransition.NextState());  // Done!
+      if (mTransition.NextStateIsTerminal()) {
+        return Some(mTransition.NextStateAsTerminal());  // Done!
       }
     }
 
     if (aLength == 0) {
       // We finished right at a transition point. Just wait for more data.
       return Nothing();
     }
 
     // If the next state is unbuffered, deliver what we can and then wait.
     if (mTransition.Buffering() == BufferingStrategy::UNBUFFERED) {
       LexerTransition<State> unbufferedTransition =
         aFunc(mTransition.UnbufferedState(), aInput, aLength);
-      if (IsTerminalState(unbufferedTransition.NextState())) {
+      if (unbufferedTransition.NextStateIsTerminal()) {
         mTransition = unbufferedTransition;
-        return Some(mTransition.NextState());  // Done!
+        return Some(mTransition.NextStateAsTerminal());  // Done!
       }
       MOZ_ASSERT(mTransition.UnbufferedState() ==
                    unbufferedTransition.NextState());
 
       mToReadUnbuffered = mTransition.Size() - aLength;
       return Nothing();  // Need more input.
     }
-    
+
     // If the next state is buffered, buffer what we can and then wait.
     MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::BUFFERED);
     if (!mBuffer.reserve(mTransition.Size())) {
-      return Some(State::FAILURE);  // Done due to allocation failure.
+      return Some(TerminalState::FAILURE);  // Done due to allocation failure.
     }
     mBuffer.append(aInput, aLength);
     return Nothing();  // Need more input.
   }
 
 private:
   Vector<char, InlineBufferSize> mBuffer;
   LexerTransition<State> mTransition;
--- a/image/decoders/nsBMPDecoder.cpp
+++ b/image/decoders/nsBMPDecoder.cpp
@@ -432,59 +432,49 @@ nsBMPDecoder::FinishRow()
 
 void
 nsBMPDecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
 {
   MOZ_ASSERT(!HasError(), "Shouldn't call WriteInternal after error!");
   MOZ_ASSERT(aBuffer);
   MOZ_ASSERT(aCount > 0);
 
-  Maybe<State> terminalState =
+  Maybe<TerminalState> terminalState =
     mLexer.Lex(aBuffer, aCount, [=](State aState,
                                     const char* aData, size_t aLength) {
       switch (aState) {
         case State::FILE_HEADER:      return ReadFileHeader(aData, aLength);
         case State::INFO_HEADER_SIZE: return ReadInfoHeaderSize(aData, aLength);
         case State::INFO_HEADER_REST: return ReadInfoHeaderRest(aData, aLength);
         case State::BITFIELDS:        return ReadBitfields(aData, aLength);
         case State::COLOR_TABLE:      return ReadColorTable(aData, aLength);
         case State::GAP:              return SkipGap();
         case State::PIXEL_ROW:        return ReadPixelRow(aData);
         case State::RLE_SEGMENT:      return ReadRLESegment(aData);
         case State::RLE_DELTA:        return ReadRLEDelta(aData);
         case State::RLE_ABSOLUTE:     return ReadRLEAbsolute(aData, aLength);
         default:
-          MOZ_ASSERT_UNREACHABLE("Unknown State");
-          return Transition::Terminate(State::FAILURE);
+          MOZ_CRASH("Unknown State");
       }
     });
 
-  if (!terminalState) {
-    return;  // Need more data.
-  }
-
-  if (*terminalState == State::FAILURE) {
+  if (terminalState == Some(TerminalState::FAILURE)) {
     PostDataError();
-    return;
   }
-
-  MOZ_ASSERT(*terminalState == State::SUCCESS);
-
-  return;
 }
 
 LexerTransition<nsBMPDecoder::State>
 nsBMPDecoder::ReadFileHeader(const char* aData, size_t aLength)
 {
   mPreGapLength += aLength;
 
   bool signatureOk = aData[0] == 'B' && aData[1] == 'M';
   if (!signatureOk) {
     PostDataError();
-    return Transition::Terminate(State::FAILURE);
+    return Transition::TerminateFailure();
   }
 
   // We ignore the filesize (aData + 2) and reserved (aData + 6) fields.
 
   mH.mDataOffset = LittleEndian::readUint32(aData + 10);
 
   return Transition::To(State::INFO_HEADER_SIZE, BIHSIZE_FIELD_LENGTH);
 }
@@ -501,17 +491,17 @@ nsBMPDecoder::ReadInfoHeaderSize(const c
   bool bihSizeOk = mH.mBIHSize == InfoHeaderLength::WIN_V2 ||
                    mH.mBIHSize == InfoHeaderLength::WIN_V3 ||
                    mH.mBIHSize == InfoHeaderLength::WIN_V4 ||
                    mH.mBIHSize == InfoHeaderLength::WIN_V5 ||
                    (mH.mBIHSize >= InfoHeaderLength::OS2_V2_MIN &&
                     mH.mBIHSize <= InfoHeaderLength::OS2_V2_MAX);
   if (!bihSizeOk) {
     PostDataError();
-    return Transition::Terminate(State::FAILURE);
+    return Transition::TerminateFailure();
   }
   // ICO BMPs must have a WinVMPv3 header. nsICODecoder should have already
   // terminated decoding if this isn't the case.
   MOZ_ASSERT_IF(mIsWithinICO, mH.mBIHSize == InfoHeaderLength::WIN_V3);
 
   return Transition::To(State::INFO_HEADER_REST,
                         mH.mBIHSize - BIHSIZE_FIELD_LENGTH);
 }
@@ -556,31 +546,31 @@ nsBMPDecoder::ReadInfoHeaderRest(const c
   // BMPs with negative width are invalid. Also, reject extremely wide images
   // to keep the math sane. And reject INT_MIN as a height because you can't
   // get its absolute value (because -INT_MIN is one more than INT_MAX).
   const int32_t k64KWidth = 0x0000FFFF;
   bool sizeOk = 0 <= mH.mWidth && mH.mWidth <= k64KWidth &&
                 mH.mHeight != INT_MIN;
   if (!sizeOk) {
     PostDataError();
-    return Transition::Terminate(State::FAILURE);
+    return Transition::TerminateFailure();
   }
 
   // Check mBpp and mCompression.
   bool bppCompressionOk =
     (mH.mCompression == Compression::RGB &&
       (mH.mBpp ==  1 || mH.mBpp ==  4 || mH.mBpp ==  8 ||
        mH.mBpp == 16 || mH.mBpp == 24 || mH.mBpp == 32)) ||
     (mH.mCompression == Compression::RLE8 && mH.mBpp == 8) ||
     (mH.mCompression == Compression::RLE4 && mH.mBpp == 4) ||
     (mH.mCompression == Compression::BITFIELDS &&
       (mH.mBpp == 16 || mH.mBpp == 32));
   if (!bppCompressionOk) {
     PostDataError();
-    return Transition::Terminate(State::FAILURE);
+    return Transition::TerminateFailure();
   }
 
   // Post our size to the superclass.
   uint32_t absHeight = AbsoluteHeight();
   PostSize(mH.mWidth, absHeight);
   mCurrentRow = absHeight;
 
   // Round it up to the nearest byte count, then pad to 4-byte boundary.
@@ -647,17 +637,17 @@ nsBMPDecoder::ReadBitfields(const char* 
      mBitFields.mAlpha.IsPresent());
   if (mMayHaveTransparency) {
     PostHasTransparency();
   }
 
   // We've now read all the headers. If we're doing a metadata decode, we're
   // done.
   if (IsMetadataDecode()) {
-    return Transition::Terminate(State::SUCCESS);
+    return Transition::TerminateSuccess();
   }
 
   // Set up the color table, if present; it'll be filled in by ReadColorTable().
   if (mH.mBpp <= 8) {
     mNumColors = 1 << mH.mBpp;
     if (0 < mH.mNumColors && mH.mNumColors < mNumColors) {
       mNumColors = mH.mNumColors;
     }
@@ -672,28 +662,28 @@ nsBMPDecoder::ReadBitfields(const char* 
   }
 
   MOZ_ASSERT(!mImageData, "Already have a buffer allocated?");
   IntSize targetSize = mDownscaler ? mDownscaler->TargetSize() : GetSize();
   nsresult rv = AllocateFrame(/* aFrameNum = */ 0, targetSize,
                               IntRect(IntPoint(), targetSize),
                               SurfaceFormat::B8G8R8A8);
   if (NS_FAILED(rv)) {
-    return Transition::Terminate(State::FAILURE);
+    return Transition::TerminateFailure();
   }
   MOZ_ASSERT(mImageData, "Should have a buffer now");
 
   if (mDownscaler) {
     // BMPs store their rows in reverse order, so the downscaler needs to
     // reverse them again when writing its output.
     rv = mDownscaler->BeginFrame(GetSize(), Nothing(),
                                  mImageData, mMayHaveTransparency,
                                  /* aFlipVertically = */ true);
     if (NS_FAILED(rv)) {
-      return Transition::Terminate(State::FAILURE);
+      return Transition::TerminateFailure();
     }
   }
 
   return Transition::To(State::COLOR_TABLE, mNumColors * mBytesPerColor);
 }
 
 LexerTransition<nsBMPDecoder::State>
 nsBMPDecoder::ReadColorTable(const char* aData, size_t aLength)
@@ -714,17 +704,17 @@ nsBMPDecoder::ReadColorTable(const char*
   // offset of the pixel data (mH.mDataOffset), so we can determine the length
   // of the gap (possibly zero) between the color table and the pixel data.
   //
   // If the gap is negative the file must be malformed (e.g. mH.mDataOffset
   // points into the middle of the color palette instead of past the end) and
   // we give up.
   if (mPreGapLength > mH.mDataOffset) {
     PostDataError();
-    return Transition::Terminate(State::FAILURE);
+    return Transition::TerminateFailure();
   }
   uint32_t gapLength = mH.mDataOffset - mPreGapLength;
   return Transition::To(State::GAP, gapLength);
 }
 
 LexerTransition<nsBMPDecoder::State>
 nsBMPDecoder::SkipGap()
 {
@@ -861,25 +851,25 @@ nsBMPDecoder::ReadPixelRow(const char* a
       break;
 
     default:
       MOZ_CRASH("Unsupported color depth; earlier check didn't catch it?");
   }
 
   FinishRow();
   return mCurrentRow == 0
-       ? Transition::Terminate(State::SUCCESS)
+       ? Transition::TerminateSuccess()
        : Transition::To(State::PIXEL_ROW, mPixelRowSize);
 }
 
 LexerTransition<nsBMPDecoder::State>
 nsBMPDecoder::ReadRLESegment(const char* aData)
 {
   if (mCurrentRow == 0) {
-    return Transition::Terminate(State::SUCCESS);
+    return Transition::TerminateSuccess();
   }
 
   uint8_t byte1 = uint8_t(aData[0]);
   uint8_t byte2 = uint8_t(aData[1]);
 
   if (byte1 != RLE::ESCAPE) {
     // Encoded mode consists of two bytes: byte1 specifies the number of
     // consecutive pixels to be drawn using the color index contained in
@@ -904,22 +894,22 @@ nsBMPDecoder::ReadRLESegment(const char*
     }
     return Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH);
   }
 
   if (byte2 == RLE::ESCAPE_EOL) {
     mCurrentPos = 0;
     FinishRow();
     return mCurrentRow == 0
-         ? Transition::Terminate(State::SUCCESS)
+         ? Transition::TerminateSuccess()
          : Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH);
   }
 
   if (byte2 == RLE::ESCAPE_EOF) {
-    return Transition::Terminate(State::SUCCESS);
+    return Transition::TerminateSuccess();
   }
 
   if (byte2 == RLE::ESCAPE_DELTA) {
     return Transition::To(State::RLE_DELTA, RLE::DELTA_LENGTH);
   }
 
   // Absolute mode. |byte2| gives the number of pixels. The length depends on
   // whether it's 4-bit or 8-bit RLE. Also, the length must be even (and zero
@@ -967,30 +957,30 @@ nsBMPDecoder::ReadRLEDelta(const char* a
     // Clear and commit the remaining skipped rows.
     for (int32_t line = 1; line < yDelta; line++) {
       mDownscaler->ClearRow();
       mDownscaler->CommitRow();
     }
   }
 
   return mCurrentRow == 0
-       ? Transition::Terminate(State::SUCCESS)
+       ? Transition::TerminateSuccess()
        : Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH);
 }
 
 LexerTransition<nsBMPDecoder::State>
 nsBMPDecoder::ReadRLEAbsolute(const char* aData, size_t aLength)
 {
   uint32_t n = mAbsoluteModeNumPixels;
   mAbsoluteModeNumPixels = 0;
 
   if (mCurrentPos + n > uint32_t(mH.mWidth)) {
     // Bad data. Stop decoding; at least part of the image may have been
     // decoded.
-    return Transition::Terminate(State::SUCCESS);
+    return Transition::TerminateSuccess();
   }
 
   // In absolute mode, n represents the number of pixels that follow, each of
   // which contains the color index of a single pixel.
   uint32_t* dst = RowBuffer();
   uint32_t iSrc = 0;
   uint32_t* oldPos = dst;
   if (mH.mCompression == Compression::RLE8) {
--- a/image/decoders/nsBMPDecoder.h
+++ b/image/decoders/nsBMPDecoder.h
@@ -162,19 +162,17 @@ private:
     INFO_HEADER_SIZE,
     INFO_HEADER_REST,
     BITFIELDS,
     COLOR_TABLE,
     GAP,
     PIXEL_ROW,
     RLE_SEGMENT,
     RLE_DELTA,
-    RLE_ABSOLUTE,
-    SUCCESS,
-    FAILURE
+    RLE_ABSOLUTE
   };
 
   // This is the constructor used by DecoderFactory.
   explicit nsBMPDecoder(RasterImage* aImage);
 
   // This is the constructor used by nsICODecoder.
   // XXX(seth): nsICODecoder is temporarily an exception to the rule that
   //            decoders should only be instantiated via DecoderFactory.
--- a/image/decoders/nsICODecoder.cpp
+++ b/image/decoders/nsICODecoder.cpp
@@ -165,24 +165,24 @@ nsICODecoder::FixBitmapWidth(int8_t* bih
   return true;
 }
 
 LexerTransition<ICOState>
 nsICODecoder::ReadHeader(const char* aData)
 {
   // If the third byte is 1, this is an icon. If 2, a cursor.
   if ((aData[2] != 1) && (aData[2] != 2)) {
-    return Transition::Terminate(ICOState::FAILURE);
+    return Transition::TerminateFailure();
   }
   mIsCursor = (aData[2] == 2);
 
   // The fifth and sixth bytes specify the number of resources in the file.
   mNumIcons = LittleEndian::readUint16(aData + 4);
   if (mNumIcons == 0) {
-    return Transition::Terminate(ICOState::SUCCESS); // Nothing to do.
+    return Transition::TerminateSuccess(); // Nothing to do.
   }
 
   // Downscale-during-decode can end up decoding different resources in the ICO
   // file depending on the target size. Since the resources are not necessarily
   // scaled versions of the same image, some may be transparent and some may not
   // be. We could be precise about transparency if we decoded the metadata of
   // every resource, but for now we don't and it's safest to assume that
   // transparency could be present.
@@ -252,32 +252,32 @@ nsICODecoder::ReadDirEntry(const char* a
       mBestResourceColorDepth = e.mBitCount;
       mDirEntry = e;
     }
   }
 
   if (mCurrIcon == mNumIcons) {
     // Ensure the resource we selected has an offset past the ICO headers.
     if (mDirEntry.mImageOffset < FirstResourceOffset()) {
-      return Transition::Terminate(ICOState::FAILURE);
+      return Transition::TerminateFailure();
     }
 
     // If this is a cursor, set the hotspot. We use the hotspot from the biggest
     // resource since we also use that resource for the intrinsic size.
     if (mIsCursor) {
       mImageMetadata.SetHotspot(mBiggestResourceHotSpot.width,
                                 mBiggestResourceHotSpot.height);
     }
 
     // We always report the biggest resource's size as the intrinsic size; this
     // is necessary for downscale-during-decode to work since we won't even
     // attempt to *upscale* while decoding.
     PostSize(mBiggestResourceSize.width, mBiggestResourceSize.height);
     if (IsMetadataDecode()) {
-      return Transition::Terminate(ICOState::SUCCESS);
+      return Transition::TerminateSuccess();
     }
 
     // If the resource we selected matches the downscaler's target size
     // perfectly, we don't need to do any downscaling.
     if (mDownscaler && GetRealSize() == mDownscaler->TargetSize()) {
       mDownscaler.reset();
     }
 
@@ -304,55 +304,55 @@ nsICODecoder::SniffResource(const char* 
     mContainedDecoder->SetDecoderFlags(GetDecoderFlags());
     mContainedDecoder->SetSurfaceFlags(GetSurfaceFlags());
     if (mDownscaler) {
       mContainedDecoder->SetTargetSize(mDownscaler->TargetSize());
     }
     mContainedDecoder->Init();
 
     if (!WriteToContainedDecoder(aData, PNGSIGNATURESIZE)) {
-      return Transition::Terminate(ICOState::FAILURE);
+      return Transition::TerminateFailure();
     }
 
     if (mDirEntry.mBytesInRes <= PNGSIGNATURESIZE) {
-      return Transition::Terminate(ICOState::FAILURE);
+      return Transition::TerminateFailure();
     }
 
     // Read in the rest of the PNG unbuffered.
     size_t toRead = mDirEntry.mBytesInRes - PNGSIGNATURESIZE;
     return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE,
                                     ICOState::READ_PNG,
                                     toRead);
   } else {
     // Make sure we have a sane size for the bitmap information header.
     int32_t bihSize = LittleEndian::readUint32(aData);
     if (bihSize != static_cast<int32_t>(BITMAPINFOSIZE)) {
-      return Transition::Terminate(ICOState::FAILURE);
+      return Transition::TerminateFailure();
     }
 
     // Buffer the first part of the bitmap information header.
     memcpy(mBIHraw, aData, PNGSIGNATURESIZE);
 
     // Read in the rest of the bitmap information header.
     return Transition::To(ICOState::READ_BIH,
                           BITMAPINFOSIZE - PNGSIGNATURESIZE);
   }
 }
 
 LexerTransition<ICOState>
 nsICODecoder::ReadPNG(const char* aData, uint32_t aLen)
 {
   if (!WriteToContainedDecoder(aData, aLen)) {
-    return Transition::Terminate(ICOState::FAILURE);
+    return Transition::TerminateFailure();
   }
 
   // Raymond Chen says that 32bpp only are valid PNG ICOs
   // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
   if (!static_cast<nsPNGDecoder*>(mContainedDecoder.get())->IsValidICO()) {
-    return Transition::Terminate(ICOState::FAILURE);
+    return Transition::TerminateFailure();
   }
 
   return Transition::ContinueUnbuffered(ICOState::READ_PNG);
 }
 
 LexerTransition<ICOState>
 nsICODecoder::ReadBIH(const char* aData)
 {
@@ -367,17 +367,17 @@ nsICODecoder::ReadBIH(const char* aData)
   // bitmap file header. So we create the BMP decoder via the constructor that
   // tells it to skip this, and pass in the required data (dataOffset) that
   // would have been present in the header.
   uint32_t dataOffset = bmp::FILE_HEADER_LENGTH + BITMAPINFOSIZE;
   if (mDirEntry.mBitCount <= 8) {
     // The color table is present only if BPP is <= 8.
     uint16_t numColors = GetNumColors();
     if (numColors == (uint16_t)-1) {
-      return Transition::Terminate(ICOState::FAILURE);
+      return Transition::TerminateFailure();
     }
     dataOffset += 4 * numColors;
   }
 
   // Create a BMP decoder which will do most of the work for us; the exception
   // is the AND mask, which isn't present in standalone BMPs.
   RefPtr<nsBMPDecoder> bmpDecoder = new nsBMPDecoder(mImage, dataOffset);
   mContainedDecoder = bmpDecoder;
@@ -388,33 +388,33 @@ nsICODecoder::ReadBIH(const char* aData)
     mContainedDecoder->SetTargetSize(mDownscaler->TargetSize());
   }
   mContainedDecoder->Init();
 
   // Fix the ICO height from the BIH. It needs to be halved so our BMP decoder
   // will understand, because the BMP decoder doesn't expect the alpha mask that
   // follows the BMP data in an ICO.
   if (!FixBitmapHeight(reinterpret_cast<int8_t*>(mBIHraw))) {
-    return Transition::Terminate(ICOState::FAILURE);
+    return Transition::TerminateFailure();
   }
 
   // Fix the ICO width from the BIH.
   if (!FixBitmapWidth(reinterpret_cast<int8_t*>(mBIHraw))) {
-    return Transition::Terminate(ICOState::FAILURE);
+    return Transition::TerminateFailure();
   }
 
   // Write out the BMP's bitmap info header.
   if (!WriteToContainedDecoder(mBIHraw, sizeof(mBIHraw))) {
-    return Transition::Terminate(ICOState::FAILURE);
+    return Transition::TerminateFailure();
   }
 
   // Check to make sure we have valid color settings.
   uint16_t numColors = GetNumColors();
   if (numColors == uint16_t(-1)) {
-    return Transition::Terminate(ICOState::FAILURE);
+    return Transition::TerminateFailure();
   }
 
   // Do we have an AND mask on this BMP? If so, we need to read it after we read
   // the BMP data itself.
   uint32_t bmpDataLength = bmpDecoder->GetCompressedImageSize() + 4 * numColors;
   bool hasANDMask = (BITMAPINFOSIZE + bmpDataLength) < mDirEntry.mBytesInRes;
   ICOState afterBMPState = hasANDMask ? ICOState::PREPARE_FOR_MASK
                                       : ICOState::FINISHED_RESOURCE;
@@ -424,17 +424,17 @@ nsICODecoder::ReadBIH(const char* aData)
                                   ICOState::READ_BMP,
                                   bmpDataLength);
 }
 
 LexerTransition<ICOState>
 nsICODecoder::ReadBMP(const char* aData, uint32_t aLen)
 {
   if (!WriteToContainedDecoder(aData, aLen)) {
-    return Transition::Terminate(ICOState::FAILURE);
+    return Transition::TerminateFailure();
   }
 
   return Transition::ContinueUnbuffered(ICOState::READ_BMP);
 }
 
 LexerTransition<ICOState>
 nsICODecoder::PrepareForMask()
 {
@@ -461,34 +461,34 @@ nsICODecoder::PrepareForMask()
 
   // Compute the row size for the mask.
   mMaskRowSize = ((GetRealWidth() + 31) / 32) * 4; // + 31 to round up
 
   // If the expected size of the AND mask is larger than its actual size, then
   // we must have a truncated (and therefore corrupt) AND mask.
   uint32_t expectedLength = mMaskRowSize * GetRealHeight();
   if (maskLength < expectedLength) {
-    return Transition::Terminate(ICOState::FAILURE);
+    return Transition::TerminateFailure();
   }
 
   // If we're downscaling, the mask is the wrong size for the surface we've
   // produced, so we need to downscale the mask into a temporary buffer and then
   // combine the mask's alpha values with the color values from the image.
   if (mDownscaler) {
     MOZ_ASSERT(bmpDecoder->GetImageDataLength() ==
                  mDownscaler->TargetSize().width *
                  mDownscaler->TargetSize().height *
                  sizeof(uint32_t));
     mMaskBuffer = MakeUnique<uint8_t[]>(bmpDecoder->GetImageDataLength());
     nsresult rv = mDownscaler->BeginFrame(GetRealSize(), Nothing(),
                                           mMaskBuffer.get(),
                                           /* aHasAlpha = */ true,
                                           /* aFlipVertically = */ true);
     if (NS_FAILED(rv)) {
-      return Transition::Terminate(ICOState::FAILURE);
+      return Transition::TerminateFailure();
     }
   }
 
   mCurrMaskLine = GetRealHeight();
   return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize);
 }
 
 
@@ -511,17 +511,17 @@ nsICODecoder::ReadMaskRow(const char* aD
     memset(mDownscaler->RowBuffer(), 0xFF, GetRealWidth() * sizeof(uint32_t));
 
     decoded = reinterpret_cast<uint32_t*>(mDownscaler->RowBuffer());
   } else {
     RefPtr<nsBMPDecoder> bmpDecoder =
       static_cast<nsBMPDecoder*>(mContainedDecoder.get());
     uint32_t* imageData = bmpDecoder->GetImageData();
     if (!imageData) {
-      return Transition::Terminate(ICOState::FAILURE);
+      return Transition::TerminateFailure();
     }
 
     decoded = imageData + mCurrMaskLine * GetRealWidth();
   }
 
   MOZ_ASSERT(decoded);
   uint32_t* decodedRowEnd = decoded + GetRealWidth();
 
@@ -561,17 +561,17 @@ nsICODecoder::FinishMask()
   // If we're downscaling, we now have the appropriate alpha values in
   // mMaskBuffer. We just need to transfer them to the image.
   if (mDownscaler) {
     // Retrieve the image data.
     RefPtr<nsBMPDecoder> bmpDecoder =
       static_cast<nsBMPDecoder*>(mContainedDecoder.get());
     uint8_t* imageData = reinterpret_cast<uint8_t*>(bmpDecoder->GetImageData());
     if (!imageData) {
-      return Transition::Terminate(ICOState::FAILURE);
+      return Transition::TerminateFailure();
     }
 
     // Iterate through the alpha values, copying from mask to image.
     MOZ_ASSERT(mMaskBuffer);
     MOZ_ASSERT(bmpDecoder->GetImageDataLength() > 0);
     for (size_t i = 3 ; i < bmpDecoder->GetImageDataLength() ; i += 4) {
       imageData[i] = mMaskBuffer[i];
     }
@@ -591,30 +591,30 @@ nsICODecoder::FinishMask()
 
 LexerTransition<ICOState>
 nsICODecoder::FinishResource()
 {
   // Make sure the actual size of the resource matches the size in the directory
   // entry. If not, we consider the image corrupt.
   if (mContainedDecoder->HasSize() &&
       mContainedDecoder->GetSize() != GetRealSize()) {
-    return Transition::Terminate(ICOState::FAILURE);
+    return Transition::TerminateFailure();
   }
 
-  return Transition::Terminate(ICOState::SUCCESS);
+  return Transition::TerminateSuccess();
 }
 
 void
 nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
 {
   MOZ_ASSERT(!HasError(), "Shouldn't call WriteInternal after error!");
   MOZ_ASSERT(aBuffer);
   MOZ_ASSERT(aCount > 0);
 
-  Maybe<ICOState> terminalState =
+  Maybe<TerminalState> terminalState =
     mLexer.Lex(aBuffer, aCount,
                [=](ICOState aState, const char* aData, size_t aLength) {
       switch (aState) {
         case ICOState::HEADER:
           return ReadHeader(aData);
         case ICOState::DIR_ENTRY:
           return ReadDirEntry(aData);
         case ICOState::SKIP_TO_RESOURCE:
@@ -635,31 +635,23 @@ nsICODecoder::WriteInternal(const char* 
           return ReadMaskRow(aData);
         case ICOState::FINISH_MASK:
           return FinishMask();
         case ICOState::SKIP_MASK:
           return Transition::ContinueUnbuffered(ICOState::SKIP_MASK);
         case ICOState::FINISHED_RESOURCE:
           return FinishResource();
         default:
-          MOZ_ASSERT_UNREACHABLE("Unknown ICOState");
-          return Transition::Terminate(ICOState::FAILURE);
+          MOZ_CRASH("Unknown ICOState");
       }
     });
 
-  if (!terminalState) {
-    return;  // Need more data.
+  if (terminalState == Some(TerminalState::FAILURE)) {
+    PostDataError();
   }
-
-  if (*terminalState == ICOState::FAILURE) {
-    PostDataError();
-    return;
-  }
-
-  MOZ_ASSERT(*terminalState == ICOState::SUCCESS);
 }
 
 bool
 nsICODecoder::WriteToContainedDecoder(const char* aBuffer, uint32_t aCount)
 {
   mContainedDecoder->Write(aBuffer, aCount);
   mProgress |= mContainedDecoder->TakeProgress();
   mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect());
--- a/image/decoders/nsICODecoder.h
+++ b/image/decoders/nsICODecoder.h
@@ -16,18 +16,16 @@
 
 namespace mozilla {
 namespace image {
 
 class RasterImage;
 
 enum class ICOState
 {
-  SUCCESS,
-  FAILURE,
   HEADER,
   DIR_ENTRY,
   SKIP_TO_RESOURCE,
   FOUND_RESOURCE,
   SNIFF_RESOURCE,
   READ_PNG,
   READ_BIH,
   READ_BMP,
--- a/image/decoders/nsIconDecoder.cpp
+++ b/image/decoders/nsIconDecoder.cpp
@@ -37,42 +37,34 @@ nsIconDecoder::~nsIconDecoder()
 
 void
 nsIconDecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
 {
   MOZ_ASSERT(!HasError(), "Shouldn't call WriteInternal after error!");
   MOZ_ASSERT(aBuffer);
   MOZ_ASSERT(aCount > 0);
 
-  Maybe<State> terminalState =
+  Maybe<TerminalState> terminalState =
     mLexer.Lex(aBuffer, aCount, [=](State aState,
                                     const char* aData, size_t aLength) {
       switch (aState) {
         case State::HEADER:
           return ReadHeader(aData);
         case State::ROW_OF_PIXELS:
           return ReadRowOfPixels(aData, aLength);
         case State::FINISH:
           return Finish();
         default:
-          MOZ_ASSERT_UNREACHABLE("Unknown State");
-          return Transition::Terminate(State::FAILURE);
+          MOZ_CRASH("Unknown State");
       }
     });
 
-  if (!terminalState) {
-    return;  // Need more data.
+  if (terminalState == Some(TerminalState::FAILURE)) {
+    PostDataError();
   }
-
-  if (*terminalState == State::FAILURE) {
-    PostDataError();
-    return;
-  }
-
-  MOZ_ASSERT(*terminalState == State::SUCCESS);
 }
 
 LexerTransition<nsIconDecoder::State>
 nsIconDecoder::ReadHeader(const char* aData)
 {
   // Grab the width and height.
   mWidth  = uint8_t(aData[0]);
   mHeight = uint8_t(aData[1]);
@@ -83,34 +75,34 @@ nsIconDecoder::ReadHeader(const char* aD
   // Post our size to the superclass.
   PostSize(mWidth, mHeight);
 
   // Icons have alpha.
   PostHasTransparency();
 
   // If we're doing a metadata decode, we're done.
   if (IsMetadataDecode()) {
-    return Transition::Terminate(State::SUCCESS);
+    return Transition::TerminateSuccess();
   }
 
   MOZ_ASSERT(!mImageData, "Already have a buffer allocated?");
   IntSize targetSize = mDownscaler ? mDownscaler->TargetSize() : GetSize();
   nsresult rv = AllocateFrame(0, targetSize,
                               IntRect(IntPoint(), targetSize),
                               gfx::SurfaceFormat::B8G8R8A8);
   if (NS_FAILED(rv)) {
-    return Transition::Terminate(State::FAILURE);
+    return Transition::TerminateFailure();
   }
   MOZ_ASSERT(mImageData, "Should have a buffer now");
 
   if (mDownscaler) {
     nsresult rv = mDownscaler->BeginFrame(GetSize(), Nothing(),
                                           mImageData, /* aHasAlpha = */ true);
     if (NS_FAILED(rv)) {
-      return Transition::Terminate(State::FAILURE);
+      return Transition::TerminateFailure();
     }
   }
 
   return Transition::To(State::ROW_OF_PIXELS, mBytesPerRow);
 }
 
 LexerTransition<nsIconDecoder::State>
 nsIconDecoder::ReadRowOfPixels(const char* aData, size_t aLength)
@@ -137,13 +129,13 @@ nsIconDecoder::ReadRowOfPixels(const cha
 }
 
 LexerTransition<nsIconDecoder::State>
 nsIconDecoder::Finish()
 {
   PostFrameStop();
   PostDecodeDone();
 
-  return Transition::Terminate(State::SUCCESS);
+  return Transition::TerminateSuccess();
 }
 
 } // namespace image
 } // namespace mozilla
--- a/image/decoders/nsIconDecoder.h
+++ b/image/decoders/nsIconDecoder.h
@@ -45,19 +45,17 @@ private:
   friend class DecoderFactory;
 
   // Decoders should only be instantiated via DecoderFactory.
   explicit nsIconDecoder(RasterImage* aImage);
 
   enum class State {
     HEADER,
     ROW_OF_PIXELS,
-    FINISH,
-    SUCCESS,
-    FAILURE
+    FINISH
   };
 
   LexerTransition<State> ReadHeader(const char* aData);
   LexerTransition<State> ReadRowOfPixels(const char* aData, size_t aLength);
   LexerTransition<State> Finish();
 
   StreamingLexer<State> mLexer;
   uint8_t mWidth;
--- a/image/test/gtest/TestStreamingLexer.cpp
+++ b/image/test/gtest/TestStreamingLexer.cpp
@@ -10,19 +10,17 @@
 using namespace mozilla;
 using namespace mozilla::image;
 
 enum class TestState
 {
   ONE,
   TWO,
   THREE,
-  UNBUFFERED,
-  SUCCESS,
-  FAILURE
+  UNBUFFERED
 };
 
 void
 CheckData(const char* aData, size_t aLength)
 {
   EXPECT_TRUE(aLength == 3);
   EXPECT_EQ(1, aData[0]);
   EXPECT_EQ(2, aData[1]);
@@ -36,20 +34,19 @@ DoLex(TestState aState, const char* aDat
     case TestState::ONE:
       CheckData(aData, aLength);
       return Transition::To(TestState::TWO, 3);
     case TestState::TWO:
       CheckData(aData, aLength);
       return Transition::To(TestState::THREE, 3);
     case TestState::THREE:
       CheckData(aData, aLength);
-      return Transition::Terminate(TestState::SUCCESS);
+      return Transition::TerminateSuccess();
     default:
-      EXPECT_TRUE(false);  // Shouldn't get here.
-      return Transition::Terminate(TestState::FAILURE);
+      MOZ_CRASH("Unknown TestState");
   }
 }
 
 LexerTransition<TestState>
 DoLexWithUnbuffered(TestState aState, const char* aData, size_t aLength,
                     Vector<char>& aUnbufferedVector)
 {
   switch (aState) {
@@ -60,207 +57,205 @@ DoLexWithUnbuffered(TestState aState, co
       EXPECT_TRUE(aLength <= 3);
       aUnbufferedVector.append(aData, aLength);
       return Transition::ContinueUnbuffered(TestState::UNBUFFERED);
     case TestState::TWO:
       CheckData(aUnbufferedVector.begin(), aUnbufferedVector.length());
       return Transition::To(TestState::THREE, 3);
     case TestState::THREE:
       CheckData(aData, aLength);
-      return Transition::Terminate(TestState::SUCCESS);
+      return Transition::TerminateSuccess();
     default:
-      EXPECT_TRUE(false);
-      return Transition::Terminate(TestState::FAILURE);
+      MOZ_CRASH("Unknown TestState");
   }
 }
 
 LexerTransition<TestState>
 DoLexWithUnbufferedTerminate(TestState aState, const char* aData, size_t aLength)
 {
   switch (aState) {
     case TestState::ONE:
       CheckData(aData, aLength);
       return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 3);
     case TestState::UNBUFFERED:
-      return Transition::Terminate(TestState::SUCCESS);
+      return Transition::TerminateSuccess();
     default:
-      EXPECT_TRUE(false);
-      return Transition::Terminate(TestState::FAILURE);
+      MOZ_CRASH("Unknown TestState");
   }
 }
 
 TEST(ImageStreamingLexer, SingleChunk)
 {
   StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
   char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
 
   // Test delivering all the data at once.
-  Maybe<TestState> result = lexer.Lex(data, sizeof(data), DoLex);
+  Maybe<TerminalState> result = lexer.Lex(data, sizeof(data), DoLex);
   EXPECT_TRUE(result.isSome());
-  EXPECT_EQ(TestState::SUCCESS, *result);
+  EXPECT_EQ(Some(TerminalState::SUCCESS), result);
 }
 
 TEST(ImageStreamingLexer, SingleChunkWithUnbuffered)
 {
   StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
   char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
   Vector<char> unbufferedVector;
 
   // Test delivering all the data at once.
-  Maybe<TestState> result =
+  Maybe<TerminalState> result =
     lexer.Lex(data, sizeof(data),
               [&](TestState aState, const char* aData, size_t aLength) {
       return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector);
   });
   EXPECT_TRUE(result.isSome());
-  EXPECT_EQ(TestState::SUCCESS, *result);
+  EXPECT_EQ(Some(TerminalState::SUCCESS), result);
 }
 
 TEST(ImageStreamingLexer, ChunkPerState)
 {
   StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
   char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
 
   // Test delivering in perfectly-sized chunks, one per state.
   for (unsigned i = 0 ; i < 3 ; ++i) {
-    Maybe<TestState> result = lexer.Lex(data + 3 * i, 3, DoLex);
+    Maybe<TerminalState> result = lexer.Lex(data + 3 * i, 3, DoLex);
 
     if (i == 2) {
       EXPECT_TRUE(result.isSome());
-      EXPECT_EQ(TestState::SUCCESS, *result);
+      EXPECT_EQ(Some(TerminalState::SUCCESS), result);
     } else {
       EXPECT_TRUE(result.isNothing());
     }
   }
 }
 
 TEST(ImageStreamingLexer, ChunkPerStateWithUnbuffered)
 {
   StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
   char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
   Vector<char> unbufferedVector;
 
   // Test delivering in perfectly-sized chunks, one per state.
   for (unsigned i = 0 ; i < 3 ; ++i) {
-    Maybe<TestState> result =
+    Maybe<TerminalState> result =
       lexer.Lex(data + 3 * i, 3,
                 [&](TestState aState, const char* aData, size_t aLength) {
         return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector);
     });
 
     if (i == 2) {
       EXPECT_TRUE(result.isSome());
-      EXPECT_EQ(TestState::SUCCESS, *result);
+      EXPECT_EQ(Some(TerminalState::SUCCESS), result);
     } else {
       EXPECT_TRUE(result.isNothing());
     }
   }
 }
 
 TEST(ImageStreamingLexer, OneByteChunks)
 {
   StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
   char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
 
   // Test delivering in one byte chunks.
   for (unsigned i = 0 ; i < 9 ; ++i) {
-    Maybe<TestState> result = lexer.Lex(data + i, 1, DoLex);
+    Maybe<TerminalState> result = lexer.Lex(data + i, 1, DoLex);
 
     if (i == 8) {
       EXPECT_TRUE(result.isSome());
-      EXPECT_EQ(TestState::SUCCESS, *result);
+      EXPECT_EQ(Some(TerminalState::SUCCESS), result);
     } else {
       EXPECT_TRUE(result.isNothing());
     }
   }
 }
 
 TEST(ImageStreamingLexer, OneByteChunksWithUnbuffered)
 {
   StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
   char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
   Vector<char> unbufferedVector;
 
   // Test delivering in one byte chunks.
   for (unsigned i = 0 ; i < 9 ; ++i) {
-    Maybe<TestState> result =
+    Maybe<TerminalState> result =
       lexer.Lex(data + i, 1,
                 [&](TestState aState, const char* aData, size_t aLength) {
         return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector);
     });
 
     if (i == 8) {
       EXPECT_TRUE(result.isSome());
-      EXPECT_EQ(TestState::SUCCESS, *result);
+      EXPECT_EQ(Some(TerminalState::SUCCESS), result);
     } else {
       EXPECT_TRUE(result.isNothing());
     }
   }
 }
 
 TEST(ImageStreamingLexer, TerminateSuccess)
 {
   StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
   char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
 
   // Test that Terminate is "sticky".
-  Maybe<TestState> result =
+  Maybe<TerminalState> result =
     lexer.Lex(data, sizeof(data),
               [&](TestState aState, const char* aData, size_t aLength) {
       EXPECT_TRUE(aState == TestState::ONE);
-      return Transition::Terminate(TestState::SUCCESS);
+      return Transition::TerminateSuccess();
   });
   EXPECT_TRUE(result.isSome());
-  EXPECT_EQ(TestState::SUCCESS, *result);
+  EXPECT_EQ(Some(TerminalState::SUCCESS), result);
 
   result =
     lexer.Lex(data, sizeof(data),
               [&](TestState aState, const char* aData, size_t aLength) {
       EXPECT_TRUE(false);  // Shouldn't get here.
-      return Transition::Terminate(TestState::FAILURE);
+      return Transition::TerminateFailure();
   });
   EXPECT_TRUE(result.isSome());
-  EXPECT_EQ(TestState::SUCCESS, *result);
+  EXPECT_EQ(Some(TerminalState::SUCCESS), result);
 }
 
 TEST(ImageStreamingLexer, TerminateFailure)
 {
   StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
   char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
 
   // Test that Terminate is "sticky".
-  Maybe<TestState> result =
+  Maybe<TerminalState> result =
     lexer.Lex(data, sizeof(data),
               [&](TestState aState, const char* aData, size_t aLength) {
       EXPECT_TRUE(aState == TestState::ONE);
-      return Transition::Terminate(TestState::FAILURE);
+      return Transition::TerminateFailure();
   });
   EXPECT_TRUE(result.isSome());
-  EXPECT_EQ(TestState::FAILURE, *result);
+  EXPECT_EQ(Some(TerminalState::FAILURE), result);
 
   result =
     lexer.Lex(data, sizeof(data),
               [&](TestState aState, const char* aData, size_t aLength) {
       EXPECT_TRUE(false);  // Shouldn't get here.
-      return Transition::Terminate(TestState::FAILURE);
+      return Transition::TerminateFailure();
   });
   EXPECT_TRUE(result.isSome());
-  EXPECT_EQ(TestState::FAILURE, *result);
+  EXPECT_EQ(Some(TerminalState::FAILURE), result);
 }
 
 TEST(ImageStreamingLexer, TerminateUnbuffered)
 {
   StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
   char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
 
   // Test that Terminate works during an unbuffered read.
   for (unsigned i = 0 ; i < 9 ; ++i) {
-    Maybe<TestState> result =
+    Maybe<TerminalState> result =
       lexer.Lex(data + i, 1, DoLexWithUnbufferedTerminate);
 
     if (i > 2) {
       EXPECT_TRUE(result.isSome());
-      EXPECT_EQ(TestState::SUCCESS, *result);
+      EXPECT_EQ(Some(TerminalState::SUCCESS), result);
     } else {
       EXPECT_TRUE(result.isNothing());
     }
   }
 }