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
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"));