Bug 1439855 - Splitting the BinTokenReaderTester in two;r?arai draft
authorDavid Teller <dteller@mozilla.com>
Thu, 05 Apr 2018 14:31:39 +0200
changeset 784326 f44c887b9e74918150a359dd16ea4448639fa0be
parent 784325 c180b34272865bdfdac4373d455843f7568b8027
child 784327 bb8367cd28b26bc64f7f9bda7692c720388648d8
push id106893
push userdteller@mozilla.com
push dateWed, 18 Apr 2018 11:19:13 +0000
reviewersarai
bugs1439855
milestone61.0a1
Bug 1439855 - Splitting the BinTokenReaderTester in two;r?arai As a preliminary step for introducing the BinTokenReaderMultipart, we start by splitting the BinTokenReaderTester in two. Also, we change a bit the API of the BinTokenReaderTester to uniformize with the BinTokenReaderMultipart. MozReview-Commit-ID: 4SlHaqEAZMk
js/src/frontend/BinTokenReaderBase.cpp
js/src/frontend/BinTokenReaderBase.h
js/src/frontend/BinTokenReaderTester.cpp
js/src/frontend/BinTokenReaderTester.h
js/src/fuzz-tests/testBinASTReader.cpp
js/src/jsapi-tests/testBinASTReader.cpp
js/src/jsapi-tests/testBinTokenReaderTester.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/BinTokenReaderBase.cpp
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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/. */
+
+#include "frontend/BinTokenReaderBase.h"
+
+#include "frontend/BinSource-macros.h"
+#include "gc/Zone.h"
+
+namespace js {
+namespace frontend {
+
+template<typename T> using ErrorResult = mozilla::GenericErrorResult<T>;
+
+// We use signalling NaN (which doesn't exist in the JS syntax)
+// to represent a `null` number.
+const uint64_t NULL_FLOAT_REPRESENTATION = 0x7FF0000000000001;
+
+void
+BinTokenReaderBase::updateLatestKnownGood()
+{
+    MOZ_ASSERT(current_ >= start_);
+    const size_t update = current_ - start_;
+    MOZ_ASSERT(update >= latestKnownGoodPos_);
+    latestKnownGoodPos_ = update;
+}
+
+ErrorResult<JS::Error&>
+BinTokenReaderBase::raiseError(const char* description)
+{
+    MOZ_ASSERT(!cx_->isExceptionPending());
+    TokenPos pos = this->pos();
+    JS_ReportErrorASCII(cx_, "BinAST parsing error: %s at offsets %u => %u",
+                        description, pos.begin, pos.end);
+    return cx_->alreadyReportedError();
+}
+
+ErrorResult<JS::Error&>
+BinTokenReaderBase::raiseOOM()
+{
+    ReportOutOfMemory(cx_);
+    return cx_->alreadyReportedError();
+}
+
+ErrorResult<JS::Error&>
+BinTokenReaderBase::raiseInvalidNumberOfFields(const BinKind kind, const uint32_t expected, const uint32_t got)
+{
+    Sprinter out(cx_);
+    BINJS_TRY(out.init());
+    BINJS_TRY(out.printf("In %s, invalid number of fields: expected %u, got %u",
+        describeBinKind(kind), expected, got));
+    return raiseError(out.string());
+}
+
+ErrorResult<JS::Error&>
+BinTokenReaderBase::raiseInvalidField(const char* kind, const BinField field)
+{
+    Sprinter out(cx_);
+    BINJS_TRY(out.init());
+    BINJS_TRY(out.printf("In %s, invalid field '%s'", kind, describeBinField(field)));
+    return raiseError(out.string());
+}
+
+
+size_t
+BinTokenReaderBase::offset() const
+{
+    return current_ - start_;
+}
+
+TokenPos
+BinTokenReaderBase::pos()
+{
+    return pos(offset());
+}
+
+TokenPos
+BinTokenReaderBase::pos(size_t start)
+{
+    TokenPos pos;
+    pos.begin = start;
+    pos.end = current_ - start_;
+    MOZ_ASSERT(pos.end >= pos.begin);
+    return pos;
+}
+
+JS::Result<Ok>
+BinTokenReaderBase::readBuf(uint8_t* bytes, uint32_t len)
+{
+    MOZ_ASSERT(!cx_->isExceptionPending());
+    MOZ_ASSERT(len > 0);
+
+    if (stop_ < current_ + len)
+        return raiseError("Buffer exceeds length");
+
+    for (uint32_t i = 0; i < len; ++i)
+        *bytes++ = *current_++;
+
+    return Ok();
+}
+
+JS::Result<uint8_t>
+BinTokenReaderBase::readByte()
+{
+    uint8_t byte;
+    MOZ_TRY(readBuf(&byte, 1));
+    return byte;
+}
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/BinTokenReaderBase.h
@@ -0,0 +1,151 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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/. */
+
+#ifndef frontend_BinTokenReaderBase_h
+#define frontend_BinTokenReaderBase_h
+
+#include "mozilla/Maybe.h"
+
+#include "frontend/BinToken.h"
+#include "frontend/TokenStream.h"
+
+#include "js/TypeDecls.h"
+
+
+namespace js {
+namespace frontend {
+
+using namespace mozilla;
+using namespace JS;
+
+// A constant used by tokenizers to represent a null float.
+extern const uint64_t NULL_FLOAT_REPRESENTATION;
+
+class MOZ_STACK_CLASS BinTokenReaderBase
+{
+  public:
+    template<typename T> using ErrorResult = mozilla::GenericErrorResult<T>;
+
+    /**
+     * Return the position of the latest token.
+     */
+    TokenPos pos();
+    TokenPos pos(size_t startOffset);
+    size_t offset() const;
+
+     /**
+      * Poison this tokenizer.
+      */
+    void poison();
+
+    /**
+     * Raise an error.
+     *
+     * Once `raiseError` has been called, the tokenizer is poisoned.
+     */
+    MOZ_MUST_USE ErrorResult<JS::Error&> raiseError(const char* description);
+    MOZ_MUST_USE ErrorResult<JS::Error&> raiseOOM();
+    MOZ_MUST_USE ErrorResult<JS::Error&> raiseInvalidNumberOfFields(
+        const BinKind kind, const uint32_t expected, const uint32_t got);
+    MOZ_MUST_USE ErrorResult<JS::Error&> raiseInvalidField(const char* kind,
+        const BinField field);
+
+  protected:
+    BinTokenReaderBase(JSContext* cx, const uint8_t* start, const size_t length)
+        : cx_(cx)
+        , start_(start)
+        , current_(start)
+        , stop_(start + length)
+        , latestKnownGoodPos_(0)
+    { }
+
+    /**
+     * Read a single byte.
+     */
+    MOZ_MUST_USE JS::Result<uint8_t> readByte();
+
+    /**
+     * Read several bytes.
+     *
+     * If there is not enough data, or if the tokenizer has previously been
+     * poisoned, return an error.
+     */
+    MOZ_MUST_USE JS::Result<Ok> readBuf(uint8_t* bytes, uint32_t len);
+
+    /**
+     * Read a sequence of chars, ensuring that they match an expected
+     * sequence of chars.
+     *
+     * @param value The sequence of chars to expect, NUL-terminated.
+     */
+     template <size_t N>
+     MOZ_MUST_USE JS::Result<Ok> readConst(const char (&value)[N])
+     {
+        updateLatestKnownGood();
+        if (!matchConst(value, false))
+            return raiseError("Could not find expected literal");
+        return Ok();
+     }
+
+     /**
+     * Read a sequence of chars, consuming the bytes only if they match an expected
+     * sequence of chars.
+     *
+     * @param value The sequence of chars to expect, NUL-terminated.
+     * @param expectNul If true, expect NUL in the stream, otherwise don't.
+     * @return true if `value` represents the next few chars in the
+     * internal buffer, false otherwise. If `true`, the chars are consumed,
+     * otherwise there is no side-effect.
+     */
+    template <size_t N>
+    MOZ_MUST_USE bool matchConst(const char (&value)[N], bool expectNul) {
+        MOZ_ASSERT(N > 0);
+        MOZ_ASSERT(value[N - 1] == 0);
+        MOZ_ASSERT(!cx_->isExceptionPending());
+
+        if (current_ + N - 1 > stop_)
+            return false;
+
+        // Perform lookup, without side-effects.
+        if (!std::equal(current_, current_ + N + (expectNul ? 0 : -1)/*implicit NUL*/, value))
+            return false;
+
+        // Looks like we have a match. Now perform side-effects
+        current_ += N + (expectNul ? 0 : -1);
+        updateLatestKnownGood();
+        return true;
+    }
+
+    void updateLatestKnownGood();
+
+    JSContext* cx_;
+
+    // `true` if we have encountered an error. Errors are non recoverable.
+    // Attempting to read from a poisoned tokenizer will cause assertion errors.
+    bool poisoned_;
+
+    // The first byte of the buffer. Not owned.
+    const uint8_t* start_;
+
+    // The current position.
+    const uint8_t* current_;
+
+    // The last+1 byte of the buffer.
+    const uint8_t* stop_;
+
+    // Latest known good position. Used for error reporting.
+    size_t latestKnownGoodPos_;
+
+  private:
+    BinTokenReaderBase(const BinTokenReaderBase&) = delete;
+    BinTokenReaderBase(BinTokenReaderBase&&) = delete;
+    BinTokenReaderBase& operator=(BinTokenReaderBase&) = delete;
+};
+
+} // namespace frontend
+} // namespace js
+
+#endif // frontend_BinTokenReaderBase_h
\ No newline at end of file
--- a/js/src/frontend/BinTokenReaderTester.cpp
+++ b/js/src/frontend/BinTokenReaderTester.cpp
@@ -1,345 +1,289 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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/. */
 
+
 #include "frontend/BinTokenReaderTester.h"
 
 #include "mozilla/EndianUtils.h"
+
+#include "frontend/BinSource-macros.h"
 #include "gc/Zone.h"
 
 namespace js {
 namespace frontend {
 
 using BinFields = BinTokenReaderTester::BinFields;
 using AutoList = BinTokenReaderTester::AutoList;
 using AutoTaggedTuple = BinTokenReaderTester::AutoTaggedTuple;
 using AutoTuple = BinTokenReaderTester::AutoTuple;
 
 BinTokenReaderTester::BinTokenReaderTester(JSContext* cx, const uint8_t* start, const size_t length)
-    : cx_(cx)
-    , start_(start)
-    , current_(start)
-    , stop_(start + length)
-    , latestKnownGoodPos_(0)
+    : BinTokenReaderBase(cx, start, length)
 { }
 
-BinTokenReaderTester::BinTokenReaderTester(JSContext* cx, const Vector<uint8_t>& chars)
-    : cx_(cx)
-    , start_(chars.begin())
-    , current_(chars.begin())
-    , stop_(chars.end())
-    , latestKnownGoodPos_(0)
+BinTokenReaderTester::BinTokenReaderTester(JSContext* cx, const Vector<uint8_t>& buf)
+    : BinTokenReaderBase(cx, buf.begin(), buf.length())
 { }
 
-bool
-BinTokenReaderTester::raiseError(const char* description)
+JS::Result<Ok>
+BinTokenReaderTester::readHeader()
 {
-    MOZ_ASSERT(!cx_->isExceptionPending());
-    TokenPos pos = this->pos();
-    JS_ReportErrorASCII(cx_, "BinAST parsing error: %s at offsets %u => %u",
-                        description, pos.begin, pos.end);
-    return false;
+    // This format does not have a header.
+    return Ok();
 }
 
-bool
-BinTokenReaderTester::readBuf(uint8_t* bytes, uint32_t len)
+JS::Result<bool>
+BinTokenReaderTester::readBool()
 {
-    MOZ_ASSERT(!cx_->isExceptionPending());
-    MOZ_ASSERT(len > 0);
-
-    if (stop_ < current_ + len)
-        return raiseError("Buffer exceeds length");
+    updateLatestKnownGood();
+    BINJS_MOZ_TRY_DECL(byte, readByte());
 
-    for (uint32_t i = 0; i < len; ++i)
-        *bytes++ = *current_++;
-
-    return true;
-}
-
-bool
-BinTokenReaderTester::readByte(uint8_t* byte)
-{
-    return readBuf(byte, 1);
+    switch (byte) {
+      case 0:
+        return false;
+      case 1:
+        return true;
+      case 2:
+        return raiseError("Not implemented: null boolean value");
+      default:
+        return raiseError("Invalid boolean value");
+    }
 }
 
 
-// Nullable booleans:
-//
-// 0 => false
-// 1 => true
-// 2 => null
-bool
-BinTokenReaderTester::readMaybeBool(Maybe<bool>& result)
-{
-    updateLatestKnownGood();
-    uint8_t byte;
-    if (!readByte(&byte))
-        return false;
-
-    switch (byte) {
-      case 0:
-        result = Some(false);
-        break;
-      case 1:
-        result = Some(true);
-        break;
-      case 2:
-        result = Nothing();
-        break;
-      default:
-        return raiseError("Invalid boolean value");
-    }
-    return true;
-}
-
-bool
-BinTokenReaderTester::readBool(bool& out)
-{
-    Maybe<bool> result;
-
-    if (!readMaybeBool(result))
-        return false;
-
-    if (result.isNothing())
-        return raiseError("Empty boolean value");
-
-    out = *result;
-    return true;
-}
-
 // Nullable doubles (little-endian)
 //
-// 0x7FF0000000000001 (signaling NaN) => null
+// NULL_FLOAT_REPRESENTATION (signaling NaN) => null
 // anything other 64 bit sequence => IEEE-764 64-bit floating point number
-bool
-BinTokenReaderTester::readMaybeDouble(Maybe<double>& result)
+JS::Result<double>
+BinTokenReaderTester::readDouble()
 {
     updateLatestKnownGood();
 
     uint8_t bytes[8];
     MOZ_ASSERT(sizeof(bytes) == sizeof(double));
-    if (!readBuf(reinterpret_cast<uint8_t*>(bytes), ArrayLength(bytes)))
-        return false;
+    MOZ_TRY(readBuf(reinterpret_cast<uint8_t*>(bytes), ArrayLength(bytes)));
 
     // Decode little-endian.
     const uint64_t asInt = LittleEndian::readUint64(bytes);
 
-    if (asInt == 0x7FF0000000000001) {
-        result = Nothing();
-    } else {
-        // Canonicalize NaN, just to make sure another form of signalling NaN
-        // doesn't slip past us.
-        const double asDouble = CanonicalizeNaN(BitwiseCast<double>(asInt));
-        result = Some(asDouble);
-    }
-
-    return true;
-}
+    if (asInt == NULL_FLOAT_REPRESENTATION)
+        return raiseError("Not implemented: null double value");
 
-bool
-BinTokenReaderTester::readDouble(double& out)
-{
-    Maybe<double> result;
-
-    if (!readMaybeDouble(result))
-        return false;
-
-    if (result.isNothing())
-        return raiseError("Empty double value");
-
-    out = *result;
-    return true;
+    // Canonicalize NaN, just to make sure another form of signalling NaN
+    // doesn't slip past us.
+    const double asDouble = CanonicalizeNaN(BitwiseCast<double>(asInt));
+    return asDouble;
 }
 
 // Internal uint32_t
 //
 // Encoded as 4 bytes, little-endian.
-bool
-BinTokenReaderTester::readInternalUint32(uint32_t* result)
+MOZ_MUST_USE JS::Result<uint32_t>
+BinTokenReaderTester::readInternalUint32()
 {
     uint8_t bytes[4];
     MOZ_ASSERT(sizeof(bytes) == sizeof(uint32_t));
-    if (!readBuf(bytes, 4))
-        return false;
+    MOZ_TRY(readBuf(bytes, 4));
 
     // Decode little-endian.
-    *result = LittleEndian::readUint32(bytes);
-
-    return true;
+    return LittleEndian::readUint32(bytes);
 }
 
 
 
 // Nullable strings:
 // - "<string>" (not counted in byte length)
 // - byte length (not counted in byte length)
 // - bytes (UTF-8)
 // - "</string>" (not counted in byte length)
 //
 // The special sequence of bytes `[255, 0]` (which is an invalid UTF-8 sequence)
 // is reserved to `null`.
-bool
-BinTokenReaderTester::readMaybeChars(Maybe<Chars>& out)
+JS::Result<JSAtom*>
+BinTokenReaderTester::readMaybeAtom()
 {
     updateLatestKnownGood();
 
-    if (!readConst("<string>"))
-        return false;
+    MOZ_TRY(readConst("<string>"));
+
+    RootedAtom result(cx_);
 
     // 1. Read byteLength
-    uint32_t byteLen;
-    if (!readInternalUint32(&byteLen))
-        return false;
+    BINJS_MOZ_TRY_DECL(byteLen, readInternalUint32());
+
+    // 2. Reject if we can't read
+    if (current_ + byteLen < current_) // Check for overflows
+        return raiseError("Arithmetics overflow: string is too long");
+
+    if (current_ + byteLen > stop_)
+        return raiseError("Not enough bytes to read chars");
+
+    if (byteLen == 2 && *current_ == 255 && *(current_ + 1) == 0) {
+        // 3. Special case: null string.
+        result = nullptr;
+    } else {
+        // 4. Other strings (bytes are copied)
+        BINJS_TRY_VAR(result, Atomize(cx_, (const char*)current_, byteLen));
+    }
+
+    current_ += byteLen;
+    MOZ_TRY(readConst("</string>"));
+    return result.get();
+}
+
+
+// Nullable strings:
+// - "<string>" (not counted in byte length)
+// - byte length (not counted in byte length)
+// - bytes (UTF-8)
+// - "</string>" (not counted in byte length)
+//
+// The special sequence of bytes `[255, 0]` (which is an invalid UTF-8 sequence)
+// is reserved to `null`.
+JS::Result<Ok>
+BinTokenReaderTester::readChars(Chars& out)
+{
+    updateLatestKnownGood();
+
+    MOZ_TRY(readConst("<string>"));
+
+    // 1. Read byteLength
+    BINJS_MOZ_TRY_DECL(byteLen, readInternalUint32());
 
     // 2. Reject if we can't read
     if (current_ + byteLen < current_) // Check for overflows
         return raiseError("Arithmetics overflow: string is too long");
 
     if (current_ + byteLen > stop_)
         return raiseError("Not enough bytes to read chars");
 
     if (byteLen == 2 && *current_ == 255 && *(current_ + 1) == 0) {
         // 3. Special case: null string.
-        out = Nothing();
-    } else {
-        // 4. Other strings (bytes are copied)
-        out.emplace(cx_);
-        if (!out->resize(byteLen)) {
-            ReportOutOfMemory(cx_);
-            return false;
-        }
-        PodCopy(out->begin(), current_, byteLen);
+        return raiseError("Empty string");
     }
 
+    // 4. Other strings (bytes are copied)
+    if (!out.resize(byteLen))
+        return raiseOOM();
+
+    PodCopy(out.begin(), current_, byteLen);
+
     current_ += byteLen;
-    if (!readConst("</string>"))
-        return false;
+
+    MOZ_TRY(readConst("</string>"));
+    return Ok();
+}
 
-    return true;
+JS::Result<JSAtom*>
+BinTokenReaderTester::readAtom()
+{
+    RootedAtom atom(cx_);
+    MOZ_TRY_VAR(atom, readMaybeAtom());
+
+    if (!atom)
+        return raiseError("Empty string");
+    return atom.get();
 }
 
-bool
-BinTokenReaderTester::readChars(Chars& out)
+JS::Result<BinVariant>
+BinTokenReaderTester::readVariant()
 {
-    Maybe<Chars> result;
+    MOZ_TRY(readConst("<string>"));
+    BINJS_MOZ_TRY_DECL(byteLen, readInternalUint32());
 
-    if (!readMaybeChars(result))
-        return false;
+    // 2. Reject if we can't read
+    if (current_ + byteLen < current_) // Check for overflows
+        return raiseError("Arithmetics overflow: string is too long");
 
-    if (result.isNothing())
-        return raiseError("Empty string");
-
-    out = Move(*result);
-    return true;
-}
+    if (current_ + byteLen > stop_)
+        return raiseError("Not enough bytes to read chars");
 
-template <size_t N>
-bool
-BinTokenReaderTester::matchConst(const char (&value)[N])
-{
-    MOZ_ASSERT(N > 0);
-    MOZ_ASSERT(value[N - 1] == 0);
-    MOZ_ASSERT(!cx_->isExceptionPending());
+    if (byteLen == 2 && *current_ == 255 && *(current_ + 1) == 0) {
+        // 3. Special case: null string.
+        return raiseError("Empty variant");
+    }
 
-    if (current_ + N - 1 > stop_)
-        return false;
+    BinaryASTSupport::CharSlice slice((const char*)current_, byteLen);
+    current_ += byteLen;
 
-    // Perform lookup, without side-effects.
-    if (!std::equal(current_, current_ + N - 1 /*implicit NUL*/, value))
-        return false;
+    BINJS_MOZ_TRY_DECL(variant, cx_->runtime()->binast().binVariant(cx_, slice));
+    if (!variant)
+        return raiseError("Not a variant");
 
-    // Looks like we have a match. Now perform side-effects
-    current_ += N - 1;
-    updateLatestKnownGood();
-    return true;
+    MOZ_TRY(readConst("</string>"));
+    return *variant;
 }
 
-
 // Untagged tuple:
 // - "<tuple>";
 // - contents (specified by the higher-level grammar);
 // - "</tuple>"
-bool
+JS::Result<Ok>
 BinTokenReaderTester::enterUntaggedTuple(AutoTuple& guard)
 {
-    if (!readConst("<tuple>"))
-        return false;
+    MOZ_TRY(readConst("<tuple>"));
 
     guard.init();
-    return true;
-}
-
-template <size_t N>
-bool
-BinTokenReaderTester::readConst(const char (&value)[N])
-{
-    updateLatestKnownGood();
-    if (!matchConst(value))
-        return raiseError("Could not find expected literal");
-
-    return true;
+    return Ok();
 }
 
 // Tagged tuples:
-// - "<tuple>"
-// - "<head>"
+// - "<tuple>";
+// - "<head>";
 // - non-null string `name`, followed by \0 (see `readString()`);
 // - uint32_t number of fields;
 // - array of `number of fields` non-null strings followed each by \0 (see `readString()`);
-// - "</head>"
+// - "</head>";
 // - content (specified by the higher-level grammar);
 // - "</tuple>"
-bool
+JS::Result<Ok>
 BinTokenReaderTester::enterTaggedTuple(BinKind& tag, BinFields& fields, AutoTaggedTuple& guard)
 {
     // Header
-    if (!readConst("<tuple>"))
-        return false;
-
-    if (!readConst("<head>"))
-        return false;
+    MOZ_TRY(readConst("<tuple>"));
+    MOZ_TRY(readConst("<head>"));
 
     // This would probably be much faster with a HashTable, but we don't
     // really care about the speed of BinTokenReaderTester.
     do {
 
 #define FIND_MATCH(CONSTRUCTOR, NAME) \
-        if (matchConst(#NAME "\0")) { \
+        if (matchConst(NAME, true)) { \
             tag = BinKind::CONSTRUCTOR; \
             break; \
         } // else
 
         FOR_EACH_BIN_KIND(FIND_MATCH)
 #undef FIND_MATCH
 
         // else
         return raiseError("Invalid tag");
     } while(false);
 
     // Now fields.
-    uint32_t fieldNum;
-    if (!readInternalUint32(&fieldNum))
-        return false;
+    BINJS_MOZ_TRY_DECL(fieldNum, readInternalUint32());
 
     fields.clear();
     if (!fields.reserve(fieldNum))
-        return raiseError("Out of memory");
+        return raiseOOM();
 
     for (uint32_t i = 0; i < fieldNum; ++i) {
         // This would probably be much faster with a HashTable, but we don't
         // really care about the speed of BinTokenReaderTester.
         BinField field;
         do {
 
 #define FIND_MATCH(CONSTRUCTOR, NAME) \
-            if (matchConst(#NAME "\0")) { \
+            if (matchConst(NAME, true)) { \
                 field = BinField::CONSTRUCTOR; \
                 break; \
             } // else
 
             FOR_EACH_BIN_FIELD(FIND_MATCH)
 #undef FIND_MATCH
 
             // else
@@ -354,89 +298,40 @@ BinTokenReaderTester::enterTaggedTuple(B
                 return raiseError("Duplicate field");
             }
         }
 
         fields.infallibleAppend(field); // Already checked.
     }
 
     // End of header
-
-    if (!readConst("</head>"))
-        return false;
+    MOZ_TRY(readConst("</head>"));
 
     // Enter the body.
     guard.init();
-    return true;
+    return Ok();
 }
 
 // List:
 //
 // - "<list>" (not counted in byte length);
 // - uint32_t byte length (not counted in byte length);
 // - uint32_t number of items;
 // - contents (specified by higher-level grammar);
 // - "</list>" (not counted in byte length)
 //
 // The total byte length of `number of items` + `contents` must be `byte length`.
-bool
+JS::Result<Ok>
 BinTokenReaderTester::enterList(uint32_t& items, AutoList& guard)
 {
-    if (!readConst("<list>"))
-        return false;
-
-    uint32_t byteLen;
-    if (!readInternalUint32(&byteLen))
-        return false;
-
-    const uint8_t* stop = current_ + byteLen;
-
-    if (stop < current_) // Check for overflows
-        return raiseError("Arithmetics overflow: list is too long");
-
-    if (stop > this->stop_)
-        return raiseError("Incorrect list length");
-
-    guard.init(stop);
-
-    if (!readInternalUint32(&items))
-        return false;
-
-    return true;
-}
+    MOZ_TRY(readConst("<list>"));
+    guard.init();
 
-void
-BinTokenReaderTester::updateLatestKnownGood()
-{
-    MOZ_ASSERT(current_ >= start_);
-    const size_t update = current_ - start_;
-    MOZ_ASSERT(update >= latestKnownGoodPos_);
-    latestKnownGoodPos_ = update;
-}
-
-size_t
-BinTokenReaderTester::offset() const
-{
-    return latestKnownGoodPos_;
-}
-
-TokenPos
-BinTokenReaderTester::pos()
-{
-    return pos(latestKnownGoodPos_);
-}
-
-TokenPos
-BinTokenReaderTester::pos(size_t start)
-{
-    TokenPos pos;
-    pos.begin = start;
-    pos.end = current_ - start_;
-    MOZ_ASSERT(pos.end >= pos.begin);
-    return pos;
+    MOZ_TRY_VAR(items, readInternalUint32());
+    return Ok();
 }
 
 void
 BinTokenReaderTester::AutoBase::init()
 {
     initialized_ = true;
 }
 
@@ -447,93 +342,86 @@ BinTokenReaderTester::AutoBase::AutoBase
 BinTokenReaderTester::AutoBase::~AutoBase()
 {
     // By now, the `AutoBase` must have been deinitialized by calling `done()`.
     // The only case in which we can accept not calling `done()` is if we have
     // bailed out because of an error.
     MOZ_ASSERT_IF(initialized_, reader_.cx_->isExceptionPending());
 }
 
-bool
+JS::Result<Ok>
 BinTokenReaderTester::AutoBase::checkPosition(const uint8_t* expectedEnd)
 {
     if (reader_.current_ != expectedEnd)
         return reader_.raiseError("Caller did not consume the expected set of bytes");
 
-    return true;
+    return Ok();
 }
 
 BinTokenReaderTester::AutoList::AutoList(BinTokenReaderTester& reader)
     : AutoBase(reader)
 { }
 
 void
-BinTokenReaderTester::AutoList::init(const uint8_t* expectedEnd)
+BinTokenReaderTester::AutoList::init()
 {
     AutoBase::init();
-    this->expectedEnd_ = expectedEnd;
 }
 
-bool
+JS::Result<Ok>
 BinTokenReaderTester::AutoList::done()
 {
     MOZ_ASSERT(initialized_);
     initialized_ = false;
     if (reader_.cx_->isExceptionPending()) {
         // Already errored, no need to check further.
-        return false;
+        return reader_.cx_->alreadyReportedError();
     }
 
-    // Check that we have consumed the exact number of bytes.
-    if (!checkPosition(expectedEnd_))
-        return false;
+    // Check suffix.
+    MOZ_TRY(reader_.readConst("</list>"));
 
-    // Check suffix.
-    if (!reader_.readConst("</list>"))
-        return false;
-
-    return true;
+    return Ok();
 }
 
 BinTokenReaderTester::AutoTaggedTuple::AutoTaggedTuple(BinTokenReaderTester& reader)
     : AutoBase(reader)
 { }
 
-bool
+JS::Result<Ok>
 BinTokenReaderTester::AutoTaggedTuple::done()
 {
     MOZ_ASSERT(initialized_);
     initialized_ = false;
     if (reader_.cx_->isExceptionPending()) {
         // Already errored, no need to check further.
-        return false;
+        return reader_.cx_->alreadyReportedError();
     }
 
     // Check suffix.
-    if (!reader_.readConst("</tuple>"))
-        return false;
+    MOZ_TRY(reader_.readConst("</tuple>"));
 
-    return true;
+    return Ok();
 }
 
 BinTokenReaderTester::AutoTuple::AutoTuple(BinTokenReaderTester& reader)
     : AutoBase(reader)
 { }
 
-bool
+JS::Result<Ok>
 BinTokenReaderTester::AutoTuple::done()
 {
     MOZ_ASSERT(initialized_);
     initialized_ = false;
     if (reader_.cx_->isExceptionPending()) {
         // Already errored, no need to check further.
-        return false;
+        return reader_.cx_->alreadyReportedError();
     }
 
     // Check suffix.
-    if (!reader_.readConst("</tuple>"))
-        return false;
+    MOZ_TRY(reader_.readConst("</tuple>"));
 
-    return true;
+    return Ok();
 }
 
 } // namespace frontend
 } // namespace js
+
--- a/js/src/frontend/BinTokenReaderTester.h
+++ b/js/src/frontend/BinTokenReaderTester.h
@@ -5,17 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef frontend_BinTokenReaderTester_h
 #define frontend_BinTokenReaderTester_h
 
 #include "mozilla/Maybe.h"
 
 #include "frontend/BinToken.h"
-#include "frontend/TokenStream.h"
+#include "frontend/BinTokenReaderBase.h"
 
 #include "js/TypeDecls.h"
 
 #if !defined(NIGHTLY_BUILD)
 #error "BinTokenReaderTester.* is designed to help test implementations of successive versions of JS BinaryAST. It is available only on Nightly."
 #endif // !defined(NIGHTLY_BUILD)
 
 namespace js {
@@ -44,17 +44,17 @@ using namespace JS;
  * - ...
  *
  * This token reader is designed to be API-compatible with the standard, shipped,
  * token reader. For these reasons:
  *
  * - it does not support any form of look ahead, push back;
  * - it does not support any form of error recovery.
  */
-class MOZ_STACK_CLASS BinTokenReaderTester
+class MOZ_STACK_CLASS BinTokenReaderTester: public BinTokenReaderBase
 {
   public:
     // A list of fields, in the order in which they appear in the stream.
     using BinFields = Vector<BinField, 8>;
 
     // A bunch of characters. At this stage, there is no guarantee on whether
     // they are valid UTF-8. Future versions may replace this by slice into
     // the buffer.
@@ -74,64 +74,74 @@ class MOZ_STACK_CLASS BinTokenReaderTest
 
     /**
      * Construct a token reader.
      *
      * Does NOT copy the buffer.
      */
     BinTokenReaderTester(JSContext* cx, const Vector<uint8_t>& chars);
 
+    /**
+     * Read the header of the file.
+     */
+    MOZ_MUST_USE JS::Result<Ok> readHeader();
+
     // --- Primitive values.
     //
     // Note that the underlying format allows for a `null` value for primitive
     // values.
     //
     // Reading will return an error either in case of I/O error or in case of
     // a format problem. Reading if an exception in pending is an error and
     // will cause assertion failures. Do NOT attempt to read once an exception
     // has been cleared: the token reader does NOT support recovery, by design.
 
     /**
-     * Read a single `true | false | null` value.
-     *
-     * @param out Set to `Nothing` if the data specifies that the value is `null`.
-     * Otherwise, `Some(true)` or `Some(false)`.
-     *
-     * @return false If a boolean could not be read. In this case, an error
-     * has been raised.
+     * Read a single `true | false` value.
      */
-    MOZ_MUST_USE bool readMaybeBool(Maybe<bool>& out);
-    MOZ_MUST_USE bool readBool(bool& out);
+    MOZ_MUST_USE JS::Result<bool> readBool();
 
     /**
-     * Read a single `number | null` value.
-     *
-     * @param out Set to `Nothing` if the data specifies that the value is `null`.
-     * Otherwise, `Some(x)`, where `x` is a valid `double` (i.e. either a non-NaN
-     * or a canonical NaN).
-     *
+     * Read a single `number` value.
      * @return false If a double could not be read. In this case, an error
      * has been raised.
      */
-    MOZ_MUST_USE bool readMaybeDouble(Maybe<double>& out);
-    MOZ_MUST_USE bool readDouble(double& out);
+    MOZ_MUST_USE JS::Result<double> readDouble();
 
     /**
      * Read a single `string | null` value.
      *
-     * @param out Set to `Nothing` if the data specifies that the value is `null`.
-     * Otherwise, `Some(x)`, where `x` is a `string`.
+     * Fails if that string is not valid UTF-8.
      *
-     * WARNING: At this stage, the `string` encoding has NOT been validated.
+     * The returned `JSAtom*` may be `nullptr`.
+     */
+    MOZ_MUST_USE JS::Result<JSAtom*> readMaybeAtom();
+
+    /**
+     * Read a single `string` value.
+     *
+     * Fails if that string is not valid UTF-8 or in case of `null` string.
      *
-     * @return false If a string could not be read. In this case, an error
-     * has been raised.
+     * The returned `JSAtom*` is never `nullptr`.
      */
-    MOZ_MUST_USE bool readMaybeChars(Maybe<Chars>& out);
-    MOZ_MUST_USE bool readChars(Chars& out);
+    MOZ_MUST_USE JS::Result<JSAtom*> readAtom();
+
+
+    /**
+     * Read a single `string | null` value.
+     *
+     * There is no guarantee that the string is valid UTF-8.
+     */
+    MOZ_MUST_USE JS::Result<Ok> readChars(Chars&);
+
+    /**
+     * Read a single `BinVariant | null` value.
+     */
+    MOZ_MUST_USE JS::Result<Maybe<BinVariant>> readMaybeVariant();
+    MOZ_MUST_USE JS::Result<BinVariant> readVariant();
 
     // --- Composite values.
     //
     // The underlying format does NOT allows for a `null` composite value.
     //
     // Reading will return an error either in case of I/O error or in case of
     // a format problem. Reading from a poisoned tokenizer is an error and
     // will cause assertion failures.
@@ -145,140 +155,54 @@ class MOZ_STACK_CLASS BinTokenReaderTest
      * The `guard` is dedicated to ensuring that reading the list has consumed
      * exactly all the bytes from that list. The `guard` MUST therefore be
      * destroyed at the point where the caller has reached the end of the list.
      * If the caller has consumed too few/too many bytes, this will be reported
      * in the call go `guard.done()`.
      *
      * @return out If the header of the list is invalid.
      */
-    MOZ_MUST_USE bool enterList(uint32_t& length, AutoList& guard);
+    MOZ_MUST_USE JS::Result<Ok> enterList(uint32_t& length, AutoList& guard);
 
     /**
      * Start reading a tagged tuple.
      *
      * @param tag (OUT) The tag of the tuple.
      * @param fields (OUT) The ORDERED list of fields encoded in this tuple.
      * @param guard (OUT) A guard, ensuring that we read the tagged tuple correctly.
      *
      * The `guard` is dedicated to ensuring that reading the list has consumed
      * exactly all the bytes from that tuple. The `guard` MUST therefore be
      * destroyed at the point where the caller has reached the end of the tuple.
      * If the caller has consumed too few/too many bytes, this will be reported
      * in the call go `guard.done()`.
      *
      * @return out If the header of the tuple is invalid.
      */
-    MOZ_MUST_USE bool enterTaggedTuple(BinKind& tag, BinTokenReaderTester::BinFields& fields, AutoTaggedTuple& guard);
+    MOZ_MUST_USE JS::Result<Ok> enterTaggedTuple(BinKind& tag, BinTokenReaderTester::BinFields& fields, AutoTaggedTuple& guard);
 
     /**
      * Start reading an untagged tuple.
      *
      * @param guard (OUT) A guard, ensuring that we read the tuple correctly.
      *
      * The `guard` is dedicated to ensuring that reading the list has consumed
      * exactly all the bytes from that tuple. The `guard` MUST therefore be
      * destroyed at the point where the caller has reached the end of the tuple.
      * If the caller has consumed too few/too many bytes, this will be reported
      * in the call go `guard.done()`.
      *
      * @return out If the header of the tuple is invalid.
      */
-    MOZ_MUST_USE bool enterUntaggedTuple(AutoTuple& guard);
-
-    /**
-     * Return the position of the latest token.
-     */
-    TokenPos pos();
-    TokenPos pos(size_t startOffset);
-    size_t offset() const;
-
-    /**
-     * Raise an error.
-     *
-     * Once `raiseError` has been called, the tokenizer is poisoned.
-     */
-    MOZ_MUST_USE bool raiseError(const char* description);
-
-     /**
-      * Poison this tokenizer.
-      */
-    void poison();
-
-  private:
-    /**
-     * Read a single byte.
-     */
-    MOZ_MUST_USE bool readByte(uint8_t* byte);
-
-    /**
-     * Read several bytes.
-     *
-     * If there is not enough data, or if the tokenizer has previously been
-     * poisoned, return `false` and report an exception.
-     */
-    MOZ_MUST_USE bool readBuf(uint8_t* bytes, uint32_t len);
+    MOZ_MUST_USE JS::Result<Ok> enterUntaggedTuple(AutoTuple& guard);
 
     /**
      * Read a single uint32_t.
      */
-    MOZ_MUST_USE bool readInternalUint32(uint32_t*);
-
-    /**
-     * Read a sequence of chars, ensuring that they match an expected
-     * sequence of chars.
-     *
-     * @param value The sequence of chars to expect, NUL-terminated. The NUL
-     * is not expected in the stream.
-     */
-     template <size_t N>
-     MOZ_MUST_USE bool readConst(const char (&value)[N]);
-
-     /**
-     * Read a sequence of chars, consuming the bytes only if they match an expected
-     * sequence of chars.
-     *
-     * @param value The sequence of chars to expect, NUL-terminated. The NUL
-     * is not expected in the stream.
-     * @return true if `value` (minus NUL) represents the next few chars in the
-     * internal buffer, false otherwise. If `true`, the chars are consumed,
-     * otherwise there is no side-effect.
-     */
-    template <size_t N>
-    MOZ_MUST_USE bool matchConst(const char (&value)[N]);
-
-    /**
-     * Update the "latest known good" position, which is used during error
-     * reporting.
-     */
-    void updateLatestKnownGood();
-
-  private:
-    JSContext* cx_;
-
-    // `true` if we have encountered an error. Errors are non recoverable.
-    // Attempting to read from a poisoned tokenizer will cause assertion errors.
-    bool poisoned_;
-
-    // The first byte of the buffer. Not owned.
-    const uint8_t* start_;
-
-    // The current position.
-    const uint8_t* current_;
-
-    // The last+1 byte of the buffer.
-    const uint8_t* stop_;
-
-
-    // Latest known good position. Used for error reporting.
-    size_t latestKnownGoodPos_;
-
-    BinTokenReaderTester(const BinTokenReaderTester&) = delete;
-    BinTokenReaderTester(BinTokenReaderTester&&) = delete;
-    BinTokenReaderTester& operator=(BinTokenReaderTester&) = delete;
+    MOZ_MUST_USE JS::Result<uint32_t> readInternalUint32();
 
   public:
     // The following classes are used whenever we encounter a tuple/tagged tuple/list
     // to make sure that:
     //
     // - if the construct "knows" its byte length, we have exactly consumed all
     //   the bytes (otherwise, this means that the file is corrupted, perhaps on
     //   purpose, so we need to reject the stream);
@@ -291,17 +215,17 @@ class MOZ_STACK_CLASS BinTokenReaderTest
     // Base class used by other Auto* classes.
     class MOZ_STACK_CLASS AutoBase
     {
       protected:
         explicit AutoBase(BinTokenReaderTester& reader);
         ~AutoBase();
 
         // Raise an error if we are not in the expected position.
-        MOZ_MUST_USE bool checkPosition(const uint8_t* expectedPosition);
+        MOZ_MUST_USE JS::Result<Ok> checkPosition(const uint8_t* expectedPosition);
 
         friend BinTokenReaderTester;
         void init();
 
         // Set to `true` if `init()` has been called. Reset to `false` once
         // all conditions have been checked.
         bool initialized_;
         BinTokenReaderTester& reader_;
@@ -309,55 +233,78 @@ class MOZ_STACK_CLASS BinTokenReaderTest
 
     // Guard class used to ensure that `enterList` is used properly.
     class MOZ_STACK_CLASS AutoList : public AutoBase
     {
       public:
         explicit AutoList(BinTokenReaderTester& reader);
 
         // Check that we have properly read to the end of the list.
-        MOZ_MUST_USE bool done();
+        MOZ_MUST_USE JS::Result<Ok> done();
       protected:
         friend BinTokenReaderTester;
-        void init(const uint8_t* expectedEnd);
-      private:
-        const uint8_t* expectedEnd_;
+        void init();
     };
 
     // Guard class used to ensure that `enterTaggedTuple` is used properly.
     class MOZ_STACK_CLASS AutoTaggedTuple : public AutoBase
     {
       public:
         explicit AutoTaggedTuple(BinTokenReaderTester& reader);
 
         // Check that we have properly read to the end of the tuple.
-        MOZ_MUST_USE bool done();
+        MOZ_MUST_USE JS::Result<Ok> done();
     };
 
     // Guard class used to ensure that `readTuple` is used properly.
     class MOZ_STACK_CLASS AutoTuple : public AutoBase
     {
       public:
         explicit AutoTuple(BinTokenReaderTester& reader);
 
         // Check that we have properly read to the end of the tuple.
-        MOZ_MUST_USE bool done();
+        MOZ_MUST_USE JS::Result<Ok> done();
     };
 
     // Compare a `Chars` and a string literal (ONLY a string literal).
     template <size_t N>
     static bool equals(const Chars& left, const char (&right)[N]) {
         MOZ_ASSERT(N > 0);
         MOZ_ASSERT(right[N - 1] == 0);
         if (left.length() + 1 /* implicit NUL */ != N)
             return false;
 
         if (!std::equal(left.begin(), left.end(), right))
           return false;
 
         return true;
     }
+
+    // Ensure that we are visiting the right fields.
+    template<size_t N>
+    JS::Result<Ok, JS::Error&> checkFields(const BinKind kind, const BinFields& actual,
+                                                  const BinField (&expected)[N])
+    {
+        if (actual.length() != N)
+            return raiseInvalidNumberOfFields(kind, N, actual.length());
+
+        for (size_t i = 0; i < N; ++i) {
+            if (actual[i] != expected[i])
+                return raiseInvalidField(describeBinKind(kind), actual[i]);
+        }
+
+        return Ok();
+    }
+
+    // Special case for N=0, as empty arrays are not permitted in C++
+    JS::Result<Ok, JS::Error&> checkFields0(const BinKind kind, const BinFields& actual)
+    {
+        if (actual.length() != 0)
+            return raiseInvalidNumberOfFields(kind, 0, actual.length());
+
+        return Ok();
+    }
 };
 
 } // namespace frontend
 } // namespace js
 
 #endif // frontend_BinTokenReaderTester_h
--- a/js/src/fuzz-tests/testBinASTReader.cpp
+++ b/js/src/fuzz-tests/testBinASTReader.cpp
@@ -51,17 +51,17 @@ testBinASTReaderFuzz(const uint8_t* buf,
     }
 
     js::frontend::UsedNameTracker binUsedNames(gCx);
     if (!binUsedNames.init()) {
         ReportOutOfMemory(gCx);
         return 0;
     }
 
-    js::frontend::BinASTParser reader(gCx, gCx->tempLifoAlloc(), binUsedNames, options);
+    js::frontend::BinASTParser<js::frontend::BinTokenReaderTester> reader(gCx, gCx->tempLifoAlloc(), binUsedNames, options);
 
     // Will be deallocated once `reader` goes out of scope.
     auto binParsed = reader.parse(binSource);
     RootedValue binExn(gCx);
     if (binParsed.isErr()) {
         js::GetAndClearException(gCx, &binExn);
         return 0;
     }
--- a/js/src/jsapi-tests/testBinASTReader.cpp
+++ b/js/src/jsapi-tests/testBinASTReader.cpp
@@ -305,16 +305,16 @@ BEGIN_TEST(testBinASTReaderSimpleECMAScr
 #endif // defined(XP_XIN)
     return true;
 }
 END_TEST(testBinASTReaderSimpleECMAScript2)
 
 BEGIN_TEST(testBinASTReaderMultipartECMAScript2)
 {
 #if defined(XP_WIN)
-    runTestFromPath<js::frontend::BinTokenReaderMultipart(cx, "jsapi-tests\\binast\\parser\\multipart\\");
+    runTestFromPath<js::frontend::BinTokenReaderMultipart>(cx, "jsapi-tests\\binast\\parser\\multipart\\");
 #else
     runTestFromPath<js::frontend::BinTokenReaderMultipart>(cx, "jsapi-tests/binast/parser/multipart/");
 #endif // defined(XP_XIN)
     return true;
 }
 END_TEST(testBinASTReaderMultipartECMAScript2)
 
--- a/js/src/jsapi-tests/testBinTokenReaderTester.cpp
+++ b/js/src/jsapi-tests/testBinTokenReaderTester.cpp
@@ -22,17 +22,17 @@
 
 #include "js/Vector.h"
 
 #include "jsapi-tests/tests.h"
 
 using mozilla::Maybe;
 
 using Tokenizer = js::frontend::BinTokenReaderTester;
-using Chars = Tokenizer::Chars;
+using Chars = js::frontend::BinTokenReaderTester::Chars;
 
 // Hack: These tests need access to resources, which are present in the source dir
 // but not copied by our build system. To simplify things, we chdir to the source
 // dir at the start of each test and return to the previous directory afterwards.
 
 #if defined(XP_UNIX)
 
 #include <sys/param.h>
@@ -215,20 +215,19 @@ BEGIN_TEST(testBinTokenReaderTesterSimpl
 
         Chars found_id(cx);
         const double EXPECTED_value = 3.1415;
 
         // Order of fields is deterministic.
         CHECK(fields[0] == js::frontend::BinField::Label);
         CHECK(fields[1] == js::frontend::BinField::Value);
         CHECK(tokenizer.readChars(found_id).isOk());
-        Maybe<double> found_value = tokenizer.readMaybeDouble().unwrap();
-        CHECK(found_value.isSome());
+        double found_value = tokenizer.readDouble().unwrap();
 
-        CHECK(EXPECTED_value == *found_value); // Apparently, CHECK_EQUAL doesn't work on `double`.
+        CHECK(EXPECTED_value == found_value); // Apparently, CHECK_EQUAL doesn't work on `double`.
         CHECK(Tokenizer::equals(found_id, "foo"));
         CHECK(guard.done().isOk());
     }
 
     return true;
 }
 END_TEST(testBinTokenReaderTesterSimpleTaggedTuple)
 
@@ -289,23 +288,23 @@ BEGIN_TEST(testBinTokenReaderTesterNeste
     js::Vector<uint8_t> contents(cx);
     readFull("jsapi-tests/binast/tokenizer/tester/test-nested-lists.binjs", contents);
     Tokenizer tokenizer(cx, contents);
 
     {
         uint32_t outerLength;
         Tokenizer::AutoList outerGuard(tokenizer);
         CHECK(tokenizer.enterList(outerLength, outerGuard).isOk());
-        CHECK(outerLength == 1);
+        CHECK_EQUAL(outerLength, (uint32_t)1);
 
         {
             uint32_t innerLength;
             Tokenizer::AutoList innerGuard(tokenizer);
             CHECK(tokenizer.enterList(innerLength, innerGuard).isOk());
-            CHECK(innerLength == 2);
+            CHECK_EQUAL(innerLength, (uint32_t)2);
 
             Chars found_0(cx);
             CHECK(tokenizer.readChars(found_0).isOk());
             CHECK(Tokenizer::equals(found_0, "foo"));
 
             Chars found_1(cx);
             CHECK(tokenizer.readChars(found_1).isOk());
             CHECK(Tokenizer::equals(found_1, "bar"));