js/src/frontend/BinTokenReaderTester.cpp
author Tooru Fujisawa <arai_a@mac.com>
Sat, 01 Dec 2018 04:52:05 +0900
changeset 505471 66eb1f485c1a3ea81372758bc92292c9428b17cd
parent 505464 e4712449ba4303cef134ba0b3f1bea13fbd50c4a
permissions -rw-r--r--
Bug 1511393 - Use c-basic-offset: 2 in Emacs mode line for C/C++ code. r=nbp

/* -*- 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/. */

#include "frontend/BinTokenReaderTester.h"

#include "mozilla/ArrayUtils.h"
#include "mozilla/Casting.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/PodOperations.h"

#include "frontend/BinSource-macros.h"
#include "js/Result.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, ErrorReporter* er,
                                           const uint8_t* start,
                                           const size_t length)
    : BinTokenReaderBase(cx, er, start, length) {}

BinTokenReaderTester::BinTokenReaderTester(JSContext* cx, ErrorReporter* er,
                                           const Vector<uint8_t>& buf)
    : BinTokenReaderBase(cx, er, buf.begin(), buf.length()) {}

JS::Result<Ok> BinTokenReaderTester::readHeader() {
  // This format does not have a header.
  return Ok();
}

JS::Result<bool> BinTokenReaderTester::readBool() {
  updateLatestKnownGood();
  BINJS_MOZ_TRY_DECL(byte, readByte());

  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 doubles (little-endian)
//
// NULL_FLOAT_REPRESENTATION (signaling NaN) => null
// anything other 64 bit sequence => IEEE-764 64-bit floating point number
JS::Result<double> BinTokenReaderTester::readDouble() {
  updateLatestKnownGood();

  uint8_t bytes[8];
  MOZ_ASSERT(sizeof(bytes) == sizeof(double));
  MOZ_TRY(
      readBuf(reinterpret_cast<uint8_t*>(bytes), mozilla::ArrayLength(bytes)));

  // Decode little-endian.
  const uint64_t asInt = mozilla::LittleEndian::readUint64(bytes);

  if (asInt == NULL_FLOAT_REPRESENTATION) {
    return raiseError("Not implemented: null double value");
  }

  // Canonicalize NaN, just to make sure another form of signalling NaN
  // doesn't slip past us.
  return JS::CanonicalizeNaN(mozilla::BitwiseCast<double>(asInt));
}

// Internal uint32_t
//
// Encoded as 4 bytes, little-endian.
MOZ_MUST_USE JS::Result<uint32_t> BinTokenReaderTester::readInternalUint32() {
  uint8_t bytes[4];
  MOZ_ASSERT(sizeof(bytes) == sizeof(uint32_t));
  MOZ_TRY(readBuf(bytes, 4));

  // Decode little-endian.
  return mozilla::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`.
JS::Result<JSAtom*> BinTokenReaderTester::readMaybeAtom() {
  updateLatestKnownGood();

  MOZ_TRY(readConst("<string>"));

  RootedAtom result(cx_);

  // 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.
    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();
}

JS::Result<JSAtom*> BinTokenReaderTester::readMaybeIdentifierName() {
  return readMaybeAtom();
}

JS::Result<JSAtom*> BinTokenReaderTester::readIdentifierName() {
  return readAtom();
}

JS::Result<JSAtom*> BinTokenReaderTester::readMaybePropertyKey() {
  return readMaybeAtom();
}

JS::Result<JSAtom*> BinTokenReaderTester::readPropertyKey() {
  return readAtom();
}

// 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.
    return raiseError("Empty string");
  }

  // 4. Other strings (bytes are copied)
  if (!out.resize(byteLen)) {
    return raiseOOM();
  }

  mozilla::PodCopy(out.begin(), current_, byteLen);

  current_ += byteLen;

  MOZ_TRY(readConst("</string>"));
  return Ok();
}

JS::Result<JSAtom*> BinTokenReaderTester::readAtom() {
  RootedAtom atom(cx_);
  MOZ_TRY_VAR(atom, readMaybeAtom());

  if (!atom) {
    return raiseError("Empty string");
  }
  return atom.get();
}

JS::Result<BinVariant> BinTokenReaderTester::readVariant() {
  MOZ_TRY(readConst("<string>"));
  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.
    return raiseError("Empty variant");
  }

  BinaryASTSupport::CharSlice slice((const char*)current_, byteLen);
  current_ += byteLen;

  BINJS_MOZ_TRY_DECL(variant, cx_->runtime()->binast().binVariant(cx_, slice));
  if (!variant) {
    return raiseError("Not a variant");
  }

  MOZ_TRY(readConst("</string>"));
  return *variant;
}

JS::Result<BinTokenReaderBase::SkippableSubTree>
BinTokenReaderTester::readSkippableSubTree() {
  updateLatestKnownGood();
  BINJS_MOZ_TRY_DECL(byteLen, readInternalUint32());

  if (current_ + byteLen > stop_ || current_ + byteLen < current_) {
    return raiseError("Invalid byte length in readSkippableSubTree");
  }

  const auto start = offset();

  current_ += byteLen;

  return BinTokenReaderBase::SkippableSubTree(start, byteLen);
}

// Untagged tuple:
// - "<tuple>";
// - contents (specified by the higher-level grammar);
// - "</tuple>"
JS::Result<Ok> BinTokenReaderTester::enterUntaggedTuple(AutoTuple& guard) {
  MOZ_TRY(readConst("<tuple>"));

  guard.init();
  return Ok();
}

// Tagged tuples:
// - "<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>";
// - content (specified by the higher-level grammar);
// - "</tuple>"
JS::Result<Ok> BinTokenReaderTester::enterTaggedTuple(BinKind& tag,
                                                      BinFields& fields,
                                                      AutoTaggedTuple& guard) {
  // Header
  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, true)) {       \
    tag = BinKind::CONSTRUCTOR;       \
    break;                            \
  }  // else

    FOR_EACH_BIN_KIND(FIND_MATCH)
#undef FIND_MATCH

    // else
    return raiseError("Invalid tag");
  } while (false);

  // Now fields.
  BINJS_MOZ_TRY_DECL(fieldNum, readInternalUint32());

  if (fieldNum > FIELD_NUM_MAX) {
    return raiseError("Too many fields");
  }

  fields.clear();
  if (!fields.reserve(fieldNum)) {
    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, true)) {       \
    field = BinField::CONSTRUCTOR;    \
    break;                            \
  }  // else

      FOR_EACH_BIN_FIELD(FIND_MATCH)
#undef FIND_MATCH

      // else
      return raiseError("Invalid field");
    } while (false);

    // Make sure that we do not have duplicate fields.
    // Search is linear, but again, we don't really care
    // in this implementation.
    for (uint32_t j = 0; j < i; ++j) {
      if (fields[j] == field) {
        return raiseError("Duplicate field");
      }
    }

    fields.infallibleAppend(field);  // Already checked.
  }

  // End of header
  MOZ_TRY(readConst("</head>"));

  // Enter the body.
  guard.init();
  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`.
JS::Result<Ok> BinTokenReaderTester::enterList(uint32_t& items,
                                               AutoList& guard) {
  MOZ_TRY(readConst("<list>"));
  guard.init();

  MOZ_TRY_VAR(items, readInternalUint32());
  return Ok();
}

void BinTokenReaderTester::AutoBase::init() { initialized_ = true; }

BinTokenReaderTester::AutoBase::AutoBase(BinTokenReaderTester& reader)
    : initialized_(false), reader_(reader) {}

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_.hasRaisedError());
}

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 Ok();
}

BinTokenReaderTester::AutoList::AutoList(BinTokenReaderTester& reader)
    : AutoBase(reader) {}

void BinTokenReaderTester::AutoList::init() { AutoBase::init(); }

JS::Result<Ok> BinTokenReaderTester::AutoList::done() {
  MOZ_ASSERT(initialized_);
  initialized_ = false;
  if (reader_.hasRaisedError()) {
    // Already errored, no need to check further.
    return reader_.cx_->alreadyReportedError();
  }

  // Check suffix.
  MOZ_TRY(reader_.readConst("</list>"));

  return Ok();
}

BinTokenReaderTester::AutoTaggedTuple::AutoTaggedTuple(
    BinTokenReaderTester& reader)
    : AutoBase(reader) {}

JS::Result<Ok> BinTokenReaderTester::AutoTaggedTuple::done() {
  MOZ_ASSERT(initialized_);
  initialized_ = false;
  if (reader_.hasRaisedError()) {
    // Already errored, no need to check further.
    return reader_.cx_->alreadyReportedError();
  }

  // Check suffix.
  MOZ_TRY(reader_.readConst("</tuple>"));

  return Ok();
}

BinTokenReaderTester::AutoTuple::AutoTuple(BinTokenReaderTester& reader)
    : AutoBase(reader) {}

JS::Result<Ok> BinTokenReaderTester::AutoTuple::done() {
  MOZ_ASSERT(initialized_);
  initialized_ = false;
  if (reader_.hasRaisedError()) {
    // Already errored, no need to check further.
    return reader_.cx_->alreadyReportedError();
  }

  // Check suffix.
  MOZ_TRY(reader_.readConst("</tuple>"));

  return Ok();
}

}  // namespace frontend
}  // namespace js