Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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/ParserAtom.h"
#include "mozilla/TextUtils.h" // mozilla::IsAscii
#include <memory> // std::uninitialized_fill_n
#include "jsnum.h" // CharsToNumber
#include "frontend/CompilationStencil.h"
#include "js/GCAPI.h" // JS::AutoSuppressGCAnalysis
#include "js/Printer.h" // Sprinter, QuoteString
#include "util/Identifier.h" // IsIdentifier
#include "util/StringBuilder.h" // StringBuilder
#include "util/Text.h" // AsciiDigitToNumber
#include "util/Unicode.h"
#include "vm/JSContext.h"
#include "vm/Runtime.h"
#include "vm/SelfHosting.h" // ExtendedUnclonedSelfHostedFunctionNamePrefix
#include "vm/StaticStrings.h"
#include "vm/StringType.h"
using namespace js;
using namespace js::frontend;
namespace js {
namespace frontend {
JSAtom* GetWellKnownAtom(JSContext* cx, WellKnownAtomId atomId) {
#define ASSERT_OFFSET_(NAME, _) \
static_assert(offsetof(JSAtomState, NAME) == \
int32_t(WellKnownAtomId::NAME) * \
sizeof(js::ImmutableTenuredPtr<PropertyName*>));
FOR_EACH_COMMON_PROPERTYNAME(ASSERT_OFFSET_);
#undef ASSERT_OFFSET_
#define ASSERT_OFFSET_(NAME, _) \
static_assert(offsetof(JSAtomState, NAME) == \
int32_t(WellKnownAtomId::NAME) * \
sizeof(js::ImmutableTenuredPtr<PropertyName*>));
JS_FOR_EACH_PROTOTYPE(ASSERT_OFFSET_);
#undef ASSERT_OFFSET_
#define ASSERT_OFFSET_(NAME) \
static_assert(offsetof(JSAtomState, NAME) == \
int32_t(WellKnownAtomId::NAME) * \
sizeof(js::ImmutableTenuredPtr<PropertyName*>));
JS_FOR_EACH_WELL_KNOWN_SYMBOL(ASSERT_OFFSET_);
#undef ASSERT_OFFSET_
static_assert(int32_t(WellKnownAtomId::abort) == 0,
"Unexpected order of WellKnownAtom");
return (&cx->names().abort)[int32_t(atomId)];
}
#ifdef DEBUG
void TaggedParserAtomIndex::validateRaw() {
if (isParserAtomIndex()) {
MOZ_ASSERT(toParserAtomIndex().index < IndexLimit);
} else if (isWellKnownAtomId()) {
MOZ_ASSERT(uint32_t(toWellKnownAtomId()) <
uint32_t(WellKnownAtomId::Limit));
} else if (isLength1StaticParserString()) {
// always valid
} else if (isLength2StaticParserString()) {
MOZ_ASSERT(size_t(toLength2StaticParserString()) < Length2StaticLimit);
} else if (isLength3StaticParserString()) {
// always valid
} else {
MOZ_ASSERT(isNull());
}
}
#endif
HashNumber TaggedParserAtomIndex::staticOrWellKnownHash() const {
MOZ_ASSERT(!isParserAtomIndex());
if (isWellKnownAtomId()) {
const auto& info = GetWellKnownAtomInfo(toWellKnownAtomId());
return info.hash;
}
if (isLength1StaticParserString()) {
Latin1Char content[1];
ParserAtomsTable::getLength1Content(toLength1StaticParserString(), content);
return mozilla::HashString(content, 1);
}
if (isLength2StaticParserString()) {
char content[2];
ParserAtomsTable::getLength2Content(toLength2StaticParserString(), content);
return mozilla::HashString(reinterpret_cast<const Latin1Char*>(content), 2);
}
MOZ_ASSERT(isLength3StaticParserString());
char content[3];
ParserAtomsTable::getLength3Content(toLength3StaticParserString(), content);
return mozilla::HashString(reinterpret_cast<const Latin1Char*>(content), 3);
}
template <typename CharT, typename SeqCharT>
/* static */ ParserAtom* ParserAtom::allocate(
FrontendContext* fc, LifoAlloc& alloc, InflatedChar16Sequence<SeqCharT> seq,
uint32_t length, HashNumber hash) {
constexpr size_t HeaderSize = sizeof(ParserAtom);
void* raw = alloc.alloc(HeaderSize + (sizeof(CharT) * length));
if (!raw) {
js::ReportOutOfMemory(fc);
return nullptr;
}
constexpr bool hasTwoByteChars = (sizeof(CharT) == 2);
static_assert(sizeof(CharT) == 1 || sizeof(CharT) == 2,
"CharT should be 1 or 2 byte type");
ParserAtom* entry = new (raw) ParserAtom(length, hash, hasTwoByteChars);
CharT* entryBuf = entry->chars<CharT>();
drainChar16Seq(entryBuf, seq, length);
return entry;
}
bool ParserAtom::isInstantiatedAsJSAtom() const {
if (isMarkedAtomize()) {
return true;
}
// Always use JSAtom for short strings.
if (length() < MinimumLengthForNonAtom) {
return true;
}
return false;
}
JSString* ParserAtom::instantiateString(JSContext* cx, FrontendContext* fc,
ParserAtomIndex index,
CompilationAtomCache& atomCache) const {
MOZ_ASSERT(!isInstantiatedAsJSAtom());
JSString* str;
if (hasLatin1Chars()) {
str = NewStringCopyNDontDeflateNonStaticValidLength<CanGC>(
cx, latin1Chars(), length(), gc::Heap::Tenured);
} else {
str = NewStringCopyNDontDeflateNonStaticValidLength<CanGC>(
cx, twoByteChars(), length(), gc::Heap::Tenured);
}
if (!str) {
return nullptr;
}
if (!atomCache.setAtomAt(fc, index, str)) {
return nullptr;
}
return str;
}
JSAtom* ParserAtom::instantiateAtom(JSContext* cx, FrontendContext* fc,
ParserAtomIndex index,
CompilationAtomCache& atomCache) const {
MOZ_ASSERT(isInstantiatedAsJSAtom());
JSAtom* atom;
if (hasLatin1Chars()) {
atom =
AtomizeCharsNonStaticValidLength(cx, hash(), latin1Chars(), length());
} else {
atom =
AtomizeCharsNonStaticValidLength(cx, hash(), twoByteChars(), length());
}
if (!atom) {
return nullptr;
}
if (!atomCache.setAtomAt(fc, index, atom)) {
return nullptr;
}
return atom;
}
JSAtom* ParserAtom::instantiatePermanentAtom(
JSContext* cx, FrontendContext* fc, AtomSet& atomSet, ParserAtomIndex index,
CompilationAtomCache& atomCache) const {
MOZ_ASSERT(!cx->zone());
MOZ_ASSERT(hasLatin1Chars());
MOZ_ASSERT(length() <= JSString::MAX_LENGTH);
JSAtom* atom = PermanentlyAtomizeCharsNonStaticValidLength(
cx, atomSet, hash(), latin1Chars(), length());
if (!atom) {
return nullptr;
}
if (!atomCache.setAtomAt(fc, index, atom)) {
return nullptr;
}
return atom;
}
#if defined(DEBUG) || defined(JS_JITSPEW)
void ParserAtom::dump() const {
js::Fprinter out(stderr);
out.put("\"");
dumpCharsNoQuote(out);
out.put("\"\n");
}
void ParserAtom::dumpCharsNoQuote(js::GenericPrinter& out) const {
if (hasLatin1Chars()) {
JSString::dumpCharsNoQuote<Latin1Char>(latin1Chars(), length(), out);
} else {
JSString::dumpCharsNoQuote<char16_t>(twoByteChars(), length(), out);
}
}
void ParserAtomsTable::dump(TaggedParserAtomIndex index) const {
if (index.isParserAtomIndex()) {
getParserAtom(index.toParserAtomIndex())->dump();
return;
}
if (index.isWellKnownAtomId()) {
const auto& info = GetWellKnownAtomInfo(index.toWellKnownAtomId());
js::Fprinter out(stderr);
out.put("\"");
out.put(info.content, info.length);
out.put("\"");
return;
}
if (index.isLength1StaticParserString()) {
js::Fprinter out(stderr);
out.put("\"");
dumpCharsNoQuote(out, index.toLength1StaticParserString());
out.put("\"\n");
return;
}
if (index.isLength2StaticParserString()) {
js::Fprinter out(stderr);
out.put("\"");
dumpCharsNoQuote(out, index.toLength2StaticParserString());
out.put("\"\n");
return;
}
if (index.isLength3StaticParserString()) {
js::Fprinter out(stderr);
out.put("\"");
dumpCharsNoQuote(out, index.toLength3StaticParserString());
out.put("\"\n");
return;
}
MOZ_ASSERT(index.isNull());
js::Fprinter out(stderr);
out.put("#<null>");
}
void ParserAtomsTable::dumpCharsNoQuote(js::GenericPrinter& out,
TaggedParserAtomIndex index) const {
if (index.isParserAtomIndex()) {
getParserAtom(index.toParserAtomIndex())->dumpCharsNoQuote(out);
return;
}
if (index.isWellKnownAtomId()) {
dumpCharsNoQuote(out, index.toWellKnownAtomId());
return;
}
if (index.isLength1StaticParserString()) {
dumpCharsNoQuote(out, index.toLength1StaticParserString());
return;
}
if (index.isLength2StaticParserString()) {
dumpCharsNoQuote(out, index.toLength2StaticParserString());
return;
}
if (index.isLength3StaticParserString()) {
dumpCharsNoQuote(out, index.toLength3StaticParserString());
return;
}
MOZ_ASSERT(index.isNull());
out.put("#<null>");
}
/* static */
void ParserAtomsTable::dumpCharsNoQuote(js::GenericPrinter& out,
WellKnownAtomId id) {
const auto& info = GetWellKnownAtomInfo(id);
out.put(info.content, info.length);
}
/* static */
void ParserAtomsTable::dumpCharsNoQuote(js::GenericPrinter& out,
Length1StaticParserString index) {
Latin1Char content[1];
getLength1Content(index, content);
out.putChar(content[0]);
}
/* static */
void ParserAtomsTable::dumpCharsNoQuote(js::GenericPrinter& out,
Length2StaticParserString index) {
char content[2];
getLength2Content(index, content);
out.putChar(content[0]);
out.putChar(content[1]);
}
/* static */
void ParserAtomsTable::dumpCharsNoQuote(js::GenericPrinter& out,
Length3StaticParserString index) {
char content[3];
getLength3Content(index, content);
out.putChar(content[0]);
out.putChar(content[1]);
out.putChar(content[2]);
}
#endif
ParserAtomsTable::ParserAtomsTable(LifoAlloc& alloc) : alloc_(&alloc) {}
TaggedParserAtomIndex ParserAtomsTable::addEntry(FrontendContext* fc,
EntryMap::AddPtr& addPtr,
ParserAtom* entry) {
MOZ_ASSERT(!addPtr);
ParserAtomIndex index = ParserAtomIndex(entries_.length());
if (size_t(index) >= TaggedParserAtomIndex::IndexLimit) {
ReportAllocationOverflow(fc);
return TaggedParserAtomIndex::null();
}
if (!entries_.append(entry)) {
js::ReportOutOfMemory(fc);
return TaggedParserAtomIndex::null();
}
auto taggedIndex = TaggedParserAtomIndex(index);
if (!entryMap_.add(addPtr, entry, taggedIndex)) {
js::ReportOutOfMemory(fc);
return TaggedParserAtomIndex::null();
}
return taggedIndex;
}
template <typename AtomCharT, typename SeqCharT>
TaggedParserAtomIndex ParserAtomsTable::internChar16Seq(
FrontendContext* fc, EntryMap::AddPtr& addPtr, HashNumber hash,
InflatedChar16Sequence<SeqCharT> seq, uint32_t length) {
MOZ_ASSERT(!addPtr);
ParserAtom* entry =
ParserAtom::allocate<AtomCharT>(fc, *alloc_, seq, length, hash);
if (!entry) {
return TaggedParserAtomIndex::null();
}
return addEntry(fc, addPtr, entry);
}
static const uint16_t MAX_LATIN1_CHAR = 0xff;
TaggedParserAtomIndex ParserAtomsTable::internAscii(FrontendContext* fc,
const char* asciiPtr,
uint32_t length) {
// ASCII strings are strict subsets of Latin1 strings.
const Latin1Char* latin1Ptr = reinterpret_cast<const Latin1Char*>(asciiPtr);
return internLatin1(fc, latin1Ptr, length);
}
TaggedParserAtomIndex ParserAtomsTable::internLatin1(
FrontendContext* fc, const Latin1Char* latin1Ptr, uint32_t length) {
// Check for tiny strings which are abundant in minified code.
if (auto tiny = WellKnownParserAtoms::getSingleton().lookupTinyIndex(
latin1Ptr, length)) {
return tiny;
}
// Check for well-known atom.
InflatedChar16Sequence<Latin1Char> seq(latin1Ptr, length);
SpecificParserAtomLookup<Latin1Char> lookup(seq);
if (auto wk = WellKnownParserAtoms::getSingleton().lookupChar16Seq(lookup)) {
return wk;
}
// Check for existing atom.
auto addPtr = entryMap_.lookupForAdd(lookup);
if (addPtr) {
return addPtr->value();
}
return internChar16Seq<Latin1Char>(fc, addPtr, lookup.hash(), seq, length);
}
bool IsWide(const InflatedChar16Sequence<char16_t>& seq) {
InflatedChar16Sequence<char16_t> seqCopy = seq;
while (seqCopy.hasMore()) {
char16_t ch = seqCopy.next();
if (ch > MAX_LATIN1_CHAR) {
return true;
}
}
return false;
}
template <typename AtomCharT>
TaggedParserAtomIndex ParserAtomsTable::internExternalParserAtomImpl(
FrontendContext* fc, const ParserAtom* atom) {
InflatedChar16Sequence<AtomCharT> seq(atom->chars<AtomCharT>(),
atom->length());
SpecificParserAtomLookup<AtomCharT> lookup(seq, atom->hash());
// Check for existing atom.
auto addPtr = entryMap_.lookupForAdd(lookup);
if (addPtr) {
auto index = addPtr->value();
// Copy UsedByStencilFlag and AtomizeFlag.
MOZ_ASSERT(entries_[index.toParserAtomIndex()]->hasTwoByteChars() ==
atom->hasTwoByteChars());
entries_[index.toParserAtomIndex()]->flags_ |= atom->flags_;
return index;
}
auto index =
internChar16Seq<AtomCharT>(fc, addPtr, atom->hash(), seq, atom->length());
if (!index) {
return TaggedParserAtomIndex::null();
}
// Copy UsedByStencilFlag and AtomizeFlag.
MOZ_ASSERT(entries_[index.toParserAtomIndex()]->hasTwoByteChars() ==
atom->hasTwoByteChars());
entries_[index.toParserAtomIndex()]->flags_ |= atom->flags_;
return index;
}
TaggedParserAtomIndex ParserAtomsTable::internExternalParserAtom(
FrontendContext* fc, const ParserAtom* atom) {
if (atom->hasLatin1Chars()) {
return internExternalParserAtomImpl<JS::Latin1Char>(fc, atom);
}
return internExternalParserAtomImpl<char16_t>(fc, atom);
}
bool ParserAtomsTable::addPlaceholder(FrontendContext* fc) {
ParserAtomIndex index = ParserAtomIndex(entries_.length());
if (size_t(index) >= TaggedParserAtomIndex::IndexLimit) {
ReportAllocationOverflow(fc);
return false;
}
if (!entries_.append(nullptr)) {
js::ReportOutOfMemory(fc);
return false;