/* -*- 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 #include "jsnum.h" #include "frontend/NameCollections.h" #include "vm/JSContext.h" #include "vm/Printer.h" #include "vm/Runtime.h" #include "vm/StringType.h" // // Parser-Atoms should be disabled for now. This check ensures that. // NOTE: This will be removed when the final transition patches from // JS-atoms to parser-atoms lands. // #ifdef JS_PARSER_ATOMS # error "Parser atoms define should remain disabled until this is removed." #endif using namespace js; using namespace js::frontend; namespace js { namespace frontend { static JS::OOM PARSER_ATOMS_OOM; mozilla::GenericErrorResult RaiseParserAtomsOOMError(JSContext* cx) { js::ReportOutOfMemory(cx); return mozilla::Err(PARSER_ATOMS_OOM); } bool ParserAtomEntry::equalsJSAtom(JSAtom* other) const { // Compare hashes first. if (hash_ != other->hash()) { return false; } if (length_ != other->length()) { return false; } JS::AutoCheckCannotGC nogc; if (hasTwoByteChars()) { // Compare heap-allocated 16-bit chars to atom. return other->hasLatin1Chars() ? EqualChars(twoByteChars(), other->latin1Chars(nogc), length_) : EqualChars(twoByteChars(), other->twoByteChars(nogc), length_); } MOZ_ASSERT(hasLatin1Chars()); return other->hasLatin1Chars() ? EqualChars(latin1Chars(), other->latin1Chars(nogc), length_) : EqualChars(latin1Chars(), other->twoByteChars(nogc), length_); } UniqueChars ParserAtomToPrintableString(JSContext* cx, ParserAtomId atom) { const ParserAtomEntry* entry = atom.entry(); Sprinter sprinter(cx); if (!sprinter.init()) { return nullptr; } size_t length = entry->length(); if (entry->hasLatin1Chars()) { if (!QuoteString( &sprinter, mozilla::Range(entry->latin1Chars(), length))) { return nullptr; } } else { if (!QuoteString( &sprinter, mozilla::Range(entry->twoByteChars(), length))) { return nullptr; } } return sprinter.release(); } bool ParserAtomEntry::isIndex(uint32_t* indexp) const { if (hasLatin1Chars()) { return js::CheckStringIsIndex(latin1Chars(), length(), indexp); } return js::CheckStringIsIndex(twoByteChars(), length(), indexp); } JS::Result ParserAtomEntry::toJSAtom(JSContext* cx) const { if (jsatom_) { return jsatom_; } if (hasLatin1Chars()) { jsatom_ = AtomizeChars(cx, latin1Chars(), length()); } else { jsatom_ = AtomizeChars(cx, twoByteChars(), length()); } if (!jsatom_) { return RaiseParserAtomsOOMError(cx); } return jsatom_; } bool ParserAtomEntry::toNumber(JSContext* cx, double* result) const { return hasLatin1Chars() ? CharsToNumber(cx, latin1Chars(), length(), result) : CharsToNumber(cx, twoByteChars(), length(), result); } ParserAtomsTable::ParserAtomsTable(JSContext* cx) : entrySet_(cx), wellKnownTable_(*cx->runtime()->commonParserNames) {} JS::Result ParserAtomsTable::addEntry( JSContext* cx, EntrySet::AddPtr addPtr, ParserAtomEntry&& entry) { UniquePtr uniqueEntry( cx->new_(std::move(entry))); if (!uniqueEntry) { return RaiseParserAtomsOOMError(cx); } ParserAtomEntry* entryPtr = uniqueEntry.get(); if (!entrySet_.add(addPtr, std::move(uniqueEntry))) { return RaiseParserAtomsOOMError(cx); } ParserAtomId id(entryPtr); return id; } static const uint16_t MAX_LATIN1_CHAR = 0xff; template static void DrainChar16Seq(CharT* buf, InflatedChar16Sequence seq) { static_assert( std::is_same_v || std::is_same_v, "Invalid target buffer type."); CharT* cur = buf; while (seq.hasMore()) { char16_t ch = seq.next(); if constexpr (std::is_same_v) { MOZ_ASSERT(ch <= MAX_LATIN1_CHAR); } *cur = ch; cur++; } } template JS::Result ParserAtomsTable::internChar16Seq( JSContext* cx, EntrySet::AddPtr add, InflatedChar16Sequence seq, uint32_t length, HashNumber hash) { using UniqueCharsT = mozilla::UniquePtr; UniqueCharsT copy(cx->pod_malloc(length)); if (!copy) { return RaiseParserAtomsOOMError(cx); } DrainChar16Seq(copy.get(), seq); ParserAtomEntry ent = ParserAtomEntry::make(std::move(copy), length, hash); return addEntry(cx, add, std::move(ent)); } template JS::Result ParserAtomsTable::lookupOrInternChar16Seq( JSContext* cx, InflatedChar16Sequence seq) { // Check against well-known. ParserAtomId wk = wellKnownTable_.lookupChar16Seq(seq); if (wk) { return wk; } // Check for existing atom. SpecificParserAtomLookup lookup(seq); EntrySet::AddPtr add = entrySet_.lookupForAdd(lookup); if (add) { return ParserAtomId(add->get()); } // Compute the total length and the storage requirements. bool wide = false; uint32_t length = 0; InflatedChar16Sequence seqCopy = seq; while (seqCopy.hasMore()) { char16_t ch = seqCopy.next(); wide = wide || (ch > MAX_LATIN1_CHAR); length += 1; } HashNumber hash = lookup.hash(); return wide ? internChar16Seq(cx, add, seq, length, hash) : internChar16Seq(cx, add, seq, length, hash); } JS::Result ParserAtomsTable::internChar16( JSContext* cx, const char16_t* char16Ptr, uint32_t length) { InflatedChar16Sequence seq(char16Ptr, length); return lookupOrInternChar16Seq(cx, seq); } JS::Result ParserAtomsTable::internAscii( JSContext* cx, const char* asciiPtr, uint32_t length) { const Latin1Char* latin1Ptr = reinterpret_cast(asciiPtr); return internLatin1(cx, latin1Ptr, length); } JS::Result ParserAtomsTable::internLatin1( JSContext* cx, const Latin1Char* latin1Ptr, uint32_t length) { // ASCII strings are strict subsets of Latin1 strings, an so can be used // in the same (const) ways. InflatedChar16Sequence seq(latin1Ptr, length); // Check against well-known. ParserAtomId wk = wellKnownTable_.lookupChar16Seq(seq); if (wk) { return wk; } // Look up. SpecificParserAtomLookup lookup(seq); EntrySet::AddPtr add = entrySet_.lookupForAdd(lookup); if (add) { return ParserAtomId(add->get()); } // Existing entry not found, heap-allocate a copy and add it to the table. UniqueLatin1Chars copy = js::DuplicateString(cx, latin1Ptr, length); if (!copy) { return RaiseParserAtomsOOMError(cx); } ParserAtomEntry ent = ParserAtomEntry::make(std::move(copy), length, lookup.hash()); return addEntry(cx, add, std::move(ent)); } JS::Result ParserAtomsTable::internUtf8( JSContext* cx, const mozilla::Utf8Unit* utf8Ptr, uint32_t length) { // If source text is ASCII, then the length of the target char buffer // is the same as the length of the UTF8 input. Convert it to a Latin1 // encoded string on the heap. UTF8Chars utf8(utf8Ptr, length); if (FindSmallestEncoding(utf8) == JS::SmallestEncoding::ASCII) { // As ascii strings are a subset of Latin1 strings, and each encoding // unit is the same size, we can reliably cast this `Utf8Unit*` // to a `Latin1Char*`. const Latin1Char* latin1Ptr = reinterpret_cast(utf8Ptr); return internLatin1(cx, latin1Ptr, length); } InflatedChar16Sequence seq(utf8Ptr, length); // Otherwise, slowpath lookup/interning path that identifies the // proper target encoding. return lookupOrInternChar16Seq(cx, seq); } JS::Result ParserAtomsTable::internJSAtom(JSContext* cx, JSAtom* atom) { JS::AutoCheckCannotGC nogc; auto result = atom->hasLatin1Chars() ? internLatin1(cx, atom->latin1Chars(nogc), atom->length()) : internChar16(cx, atom->twoByteChars(nogc), atom->length()); if (result.isErr()) { return result; } ParserAtomId id = result.unwrap(); id.entry()->setAtom(atom); return id; } static void FillChar16Buffer(char16_t* buf, const ParserAtomEntry* ent) { if (ent->hasLatin1Chars()) { std::copy(ent->latin1Chars(), ent->latin1Chars() + ent->length(), buf); } else { std::copy(ent->twoByteChars(), ent->twoByteChars() + ent->length(), buf); } } JS::Result ParserAtomsTable::concatAtoms( JSContext* cx, ParserAtomId prefix, ParserAtomId suffix) { const ParserAtomEntry* prefixEntry = prefix.entry(); const ParserAtomEntry* suffixEntry = suffix.entry(); bool latin1 = prefixEntry->hasLatin1Chars() && suffixEntry->hasLatin1Chars(); size_t prefixLength = prefixEntry->length(); size_t suffixLength = suffixEntry->length(); size_t concatLength = prefixLength + suffixLength; if (latin1) { // Concatenate a latin1 string and add it to the table. UniqueLatin1Chars copy(cx->pod_malloc(concatLength)); if (!copy) { return RaiseParserAtomsOOMError(cx); } mozilla::PodCopy(copy.get(), prefixEntry->latin1Chars(), prefixLength); mozilla::PodCopy(copy.get() + prefixLength, suffixEntry->latin1Chars(), suffixLength); InflatedChar16Sequence seq(copy.get(), concatLength); // Check against well-known. ParserAtomId wk = wellKnownTable_.lookupChar16Seq(seq); if (wk) { return wk; } SpecificParserAtomLookup lookup(seq); EntrySet::AddPtr add = entrySet_.lookupForAdd(lookup); if (add) { return ParserAtomId(add->get()); } ParserAtomEntry ent = ParserAtomEntry::make(std::move(copy), concatLength, lookup.hash()); return addEntry(cx, add, std::move(ent)); } // Concatenate a char16 string and add it to the table. UniqueTwoByteChars copy(cx->pod_malloc(concatLength)); if (!copy) { return RaiseParserAtomsOOMError(cx); } FillChar16Buffer(copy.get(), prefixEntry); FillChar16Buffer(copy.get() + prefixLength, suffixEntry); InflatedChar16Sequence seq(copy.get(), concatLength); // Check against well-known. ParserAtomId wk = wellKnownTable_.lookupChar16Seq(seq); if (wk) { return wk; } SpecificParserAtomLookup lookup(seq); EntrySet::AddPtr add = entrySet_.lookupForAdd(lookup); if (add) { return ParserAtomId(add->get()); } ParserAtomEntry ent = ParserAtomEntry::make(std::move(copy), concatLength, lookup.hash()); return addEntry(cx, add, std::move(ent)); } template ParserAtomId WellKnownParserAtoms::lookupChar16Seq( InflatedChar16Sequence seq) const { SpecificParserAtomLookup lookup(seq); EntrySet::Ptr get = entrySet_.readonlyThreadsafeLookup(lookup); if (get) { return ParserAtomId(get->get()); } return ParserAtomId::Invalid(); } bool WellKnownParserAtoms::initSingle(JSContext* cx, ParserNameId* name, const char* str) { MOZ_ASSERT(name != nullptr); unsigned int len = strlen(str); MOZ_ASSERT(FindSmallestEncoding(UTF8Chars(str, len)) == JS::SmallestEncoding::ASCII); UniqueLatin1Chars copy(cx->pod_malloc(len)); if (!copy) { return false; } mozilla::PodCopy(copy.get(), reinterpret_cast(str), len); InflatedChar16Sequence seq(copy.get(), len); SpecificParserAtomLookup lookup(seq); ParserAtomEntry ent = ParserAtomEntry::make(std::move(copy), len, lookup.hash()); UniquePtr uniqueEntry( cx->new_(std::move(ent))); if (!uniqueEntry) { return false; } ParserNameId nm(uniqueEntry.get()); if (!entrySet_.putNew(lookup, std::move(uniqueEntry))) { return false; } *name = nm; return true; } bool WellKnownParserAtoms::init(JSContext* cx) { #define COMMON_NAME_INIT(idpart, id, text) \ if (!initSingle(cx, &(id), text)) { \ return false; \ } FOR_EACH_COMMON_PROPERTYNAME(COMMON_NAME_INIT) #undef COMMON_NAME_INIT return true; } } /* namespace frontend */ } /* namespace js */ bool JSRuntime::initializeParserAtoms(JSContext* cx) { #ifdef JS_PARSER_ATOMS MOZ_ASSERT(!commonParserNames); if (parentRuntime) { commonParserNames = parentRuntime->commonParserNames; return true; } UniquePtr names( js_new(cx)); if (!names || !names->init(cx)) { return false; } commonParserNames = names.release(); #else commonParserNames = nullptr; #endif // JS_PARSER_ATOMS return true; } void JSRuntime::finishParserAtoms() { #ifdef JS_PARSER_ATOMS if (!parentRuntime) { js_delete(commonParserNames.ref()); } #else MOZ_ASSERT(!commonParserNames); #endif // JS_PARSER_ATOMS }