| /* |
| * Copyright (C) 2014-2015 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #pragma once |
| |
| #if ENABLE(CONTENT_EXTENSIONS) |
| |
| #include "ContentExtensionsDebugging.h" |
| #include "NFA.h" |
| #include <unicode/utypes.h> |
| #include <wtf/ASCIICType.h> |
| #include <wtf/HashMap.h> |
| #include <wtf/Vector.h> |
| #include <wtf/text/StringBuilder.h> |
| #include <wtf/text/WTFString.h> |
| |
| namespace WebCore { |
| |
| namespace ContentExtensions { |
| |
| enum class AtomQuantifier : uint8_t { |
| One, |
| ZeroOrOne, |
| ZeroOrMore, |
| OneOrMore |
| }; |
| |
| class Term { |
| public: |
| Term(); |
| Term(char character, bool isCaseSensitive); |
| |
| enum UniversalTransitionTag { UniversalTransition }; |
| explicit Term(UniversalTransitionTag); |
| |
| enum CharacterSetTermTag { CharacterSetTerm }; |
| explicit Term(CharacterSetTermTag, bool isInverted); |
| |
| enum GroupTermTag { GroupTerm }; |
| explicit Term(GroupTermTag); |
| |
| enum EndOfLineAssertionTermTag { EndOfLineAssertionTerm }; |
| explicit Term(EndOfLineAssertionTermTag); |
| |
| Term(const Term& other); |
| Term(Term&& other); |
| |
| enum EmptyValueTag { EmptyValue }; |
| Term(EmptyValueTag); |
| |
| ~Term(); |
| |
| bool isValid() const; |
| |
| // CharacterSet terms only. |
| void addCharacter(UChar character, bool isCaseSensitive); |
| |
| // Group terms only. |
| void extendGroupSubpattern(const Term&); |
| |
| void quantify(const AtomQuantifier&); |
| |
| ImmutableCharNFANodeBuilder generateGraph(NFA&, ImmutableCharNFANodeBuilder& source, const ActionList& finalActions) const; |
| void generateGraph(NFA&, ImmutableCharNFANodeBuilder& source, uint32_t destination) const; |
| |
| bool isEndOfLineAssertion() const; |
| |
| bool matchesAtLeastOneCharacter() const; |
| |
| // Matches any string, the empty string included. |
| // This is very conservative. Patterns matching any string can return false here. |
| bool isKnownToMatchAnyString() const; |
| |
| // Return true if the term can only match input of a single fixed length. |
| bool hasFixedLength() const; |
| |
| Term& operator=(const Term& other); |
| Term& operator=(Term&& other); |
| |
| bool operator==(const Term& other) const; |
| |
| unsigned hash() const; |
| |
| bool isEmptyValue() const; |
| |
| #if CONTENT_EXTENSIONS_STATE_MACHINE_DEBUGGING |
| String toString() const; |
| #endif |
| |
| #if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING |
| size_t memoryUsed() const; |
| #endif |
| |
| private: |
| // This is exact for character sets but conservative for groups. |
| // The return value can be false for a group equivalent to a universal transition. |
| bool isUniversalTransition() const; |
| |
| void generateSubgraphForAtom(NFA&, ImmutableCharNFANodeBuilder& source, const ImmutableCharNFANodeBuilder& destination) const; |
| void generateSubgraphForAtom(NFA&, ImmutableCharNFANodeBuilder& source, uint32_t destination) const; |
| |
| void destroy(); |
| |
| enum class TermType : uint8_t { |
| Empty, |
| CharacterSet, |
| Group |
| }; |
| |
| TermType m_termType { TermType::Empty }; |
| AtomQuantifier m_quantifier { AtomQuantifier::One }; |
| |
| class CharacterSet { |
| public: |
| void set(UChar character) |
| { |
| RELEASE_ASSERT(character < 128); |
| m_characters[character / 64] |= (uint64_t(1) << (character % 64)); |
| } |
| |
| bool get(UChar character) const |
| { |
| RELEASE_ASSERT(character < 128); |
| return m_characters[character / 64] & (uint64_t(1) << (character % 64)); |
| } |
| |
| void invert() |
| { |
| ASSERT(!m_inverted); |
| m_inverted = true; |
| } |
| |
| bool inverted() const { return m_inverted; } |
| |
| unsigned bitCount() const |
| { |
| return WTF::bitCount(m_characters[0]) + WTF::bitCount(m_characters[1]); |
| } |
| |
| bool operator==(const CharacterSet& other) const |
| { |
| return other.m_inverted == m_inverted |
| && other.m_characters[0] == m_characters[0] |
| && other.m_characters[1] == m_characters[1]; |
| } |
| |
| unsigned hash() const |
| { |
| return WTF::pairIntHash(WTF::pairIntHash(WTF::intHash(m_characters[0]), WTF::intHash(m_characters[1])), m_inverted); |
| } |
| private: |
| bool m_inverted { false }; |
| uint64_t m_characters[2] { 0, 0 }; |
| }; |
| |
| struct Group { |
| Vector<Term> terms; |
| |
| bool operator==(const Group& other) const |
| { |
| return other.terms == terms; |
| } |
| |
| unsigned hash() const |
| { |
| unsigned hash = 6421749; |
| for (const Term& term : terms) { |
| unsigned termHash = term.hash(); |
| hash = (hash << 16) ^ ((termHash << 11) ^ hash); |
| hash += hash >> 11; |
| } |
| return hash; |
| } |
| }; |
| |
| union AtomData { |
| AtomData() |
| : invalidTerm(0) |
| { |
| } |
| ~AtomData() |
| { |
| } |
| |
| char invalidTerm; |
| CharacterSet characterSet; |
| Group group; |
| } m_atomData; |
| }; |
| |
| #if CONTENT_EXTENSIONS_STATE_MACHINE_DEBUGGING |
| inline String quantifierToString(AtomQuantifier quantifier) |
| { |
| switch (quantifier) { |
| case AtomQuantifier::One: |
| return ""; |
| case AtomQuantifier::ZeroOrOne: |
| return "?"; |
| case AtomQuantifier::ZeroOrMore: |
| return "*"; |
| case AtomQuantifier::OneOrMore: |
| return "+"; |
| } |
| } |
| |
| inline String Term::toString() const |
| { |
| switch (m_termType) { |
| case TermType::Empty: |
| return "(Empty)"; |
| case TermType::CharacterSet: { |
| StringBuilder builder; |
| builder.append('['); |
| for (UChar c = 0; c < 128; c++) { |
| if (m_atomData.characterSet.get(c)) { |
| if (isASCIIPrintable(c) && !isASCIISpace(c)) |
| builder.append(c); |
| else { |
| builder.append('\\'); |
| builder.append('u'); |
| builder.appendNumber(c); |
| } |
| } |
| } |
| builder.append(']'); |
| builder.append(quantifierToString(m_quantifier)); |
| return builder.toString(); |
| } |
| case TermType::Group: { |
| StringBuilder builder; |
| builder.append('('); |
| for (const Term& term : m_atomData.group.terms) |
| builder.append(term.toString()); |
| builder.append(')'); |
| builder.append(quantifierToString(m_quantifier)); |
| return builder.toString(); |
| } |
| } |
| } |
| #endif |
| |
| inline Term::Term() |
| { |
| } |
| |
| inline Term::Term(char character, bool isCaseSensitive) |
| : m_termType(TermType::CharacterSet) |
| { |
| new (NotNull, &m_atomData.characterSet) CharacterSet(); |
| addCharacter(character, isCaseSensitive); |
| } |
| |
| inline Term::Term(UniversalTransitionTag) |
| : m_termType(TermType::CharacterSet) |
| { |
| new (NotNull, &m_atomData.characterSet) CharacterSet(); |
| for (UChar i = 1; i < 128; ++i) |
| m_atomData.characterSet.set(i); |
| } |
| |
| inline Term::Term(CharacterSetTermTag, bool isInverted) |
| : m_termType(TermType::CharacterSet) |
| { |
| new (NotNull, &m_atomData.characterSet) CharacterSet(); |
| if (isInverted) |
| m_atomData.characterSet.invert(); |
| } |
| |
| inline Term::Term(GroupTermTag) |
| : m_termType(TermType::Group) |
| { |
| new (NotNull, &m_atomData.group) Group(); |
| } |
| |
| inline Term::Term(EndOfLineAssertionTermTag) |
| : Term(CharacterSetTerm, false) |
| { |
| m_atomData.characterSet.set(0); |
| } |
| |
| inline Term::Term(const Term& other) |
| : m_termType(other.m_termType) |
| , m_quantifier(other.m_quantifier) |
| { |
| switch (m_termType) { |
| case TermType::Empty: |
| break; |
| case TermType::CharacterSet: |
| new (NotNull, &m_atomData.characterSet) CharacterSet(other.m_atomData.characterSet); |
| break; |
| case TermType::Group: |
| new (NotNull, &m_atomData.group) Group(other.m_atomData.group); |
| break; |
| } |
| } |
| |
| inline Term::Term(Term&& other) |
| : m_termType(WTFMove(other.m_termType)) |
| , m_quantifier(WTFMove(other.m_quantifier)) |
| { |
| switch (m_termType) { |
| case TermType::Empty: |
| break; |
| case TermType::CharacterSet: |
| new (NotNull, &m_atomData.characterSet) CharacterSet(WTFMove(other.m_atomData.characterSet)); |
| break; |
| case TermType::Group: |
| new (NotNull, &m_atomData.group) Group(WTFMove(other.m_atomData.group)); |
| break; |
| } |
| other.destroy(); |
| } |
| |
| inline Term::Term(EmptyValueTag) |
| : m_termType(TermType::Empty) |
| { |
| } |
| |
| inline Term::~Term() |
| { |
| destroy(); |
| } |
| |
| inline bool Term::isValid() const |
| { |
| return m_termType != TermType::Empty; |
| } |
| |
| inline void Term::addCharacter(UChar character, bool isCaseSensitive) |
| { |
| ASSERT(isASCII(character)); |
| |
| ASSERT_WITH_SECURITY_IMPLICATION(m_termType == TermType::CharacterSet); |
| if (m_termType != TermType::CharacterSet) |
| return; |
| |
| if (isCaseSensitive || !isASCIIAlpha(character)) |
| m_atomData.characterSet.set(character); |
| else { |
| m_atomData.characterSet.set(toASCIIUpper(character)); |
| m_atomData.characterSet.set(toASCIILower(character)); |
| } |
| } |
| |
| inline void Term::extendGroupSubpattern(const Term& term) |
| { |
| ASSERT_WITH_SECURITY_IMPLICATION(m_termType == TermType::Group); |
| if (m_termType != TermType::Group) |
| return; |
| m_atomData.group.terms.append(term); |
| } |
| |
| inline void Term::quantify(const AtomQuantifier& quantifier) |
| { |
| ASSERT_WITH_MESSAGE(m_quantifier == AtomQuantifier::One, "Transition to quantified term should only happen once."); |
| m_quantifier = quantifier; |
| } |
| |
| inline ImmutableCharNFANodeBuilder Term::generateGraph(NFA& nfa, ImmutableCharNFANodeBuilder& source, const ActionList& finalActions) const |
| { |
| ImmutableCharNFANodeBuilder newEnd(nfa); |
| generateGraph(nfa, source, newEnd.nodeId()); |
| newEnd.setActions(finalActions.begin(), finalActions.end()); |
| return newEnd; |
| } |
| |
| inline void Term::generateGraph(NFA& nfa, ImmutableCharNFANodeBuilder& source, uint32_t destination) const |
| { |
| ASSERT(isValid()); |
| |
| switch (m_quantifier) { |
| case AtomQuantifier::One: { |
| generateSubgraphForAtom(nfa, source, destination); |
| break; |
| } |
| case AtomQuantifier::ZeroOrOne: { |
| generateSubgraphForAtom(nfa, source, destination); |
| source.addEpsilonTransition(destination); |
| break; |
| } |
| case AtomQuantifier::ZeroOrMore: { |
| ImmutableCharNFANodeBuilder repeatStart(nfa); |
| source.addEpsilonTransition(repeatStart); |
| |
| ImmutableCharNFANodeBuilder repeatEnd(nfa); |
| generateSubgraphForAtom(nfa, repeatStart, repeatEnd); |
| repeatEnd.addEpsilonTransition(repeatStart); |
| |
| repeatEnd.addEpsilonTransition(destination); |
| source.addEpsilonTransition(destination); |
| break; |
| } |
| case AtomQuantifier::OneOrMore: { |
| ImmutableCharNFANodeBuilder repeatStart(nfa); |
| source.addEpsilonTransition(repeatStart); |
| |
| ImmutableCharNFANodeBuilder repeatEnd(nfa); |
| generateSubgraphForAtom(nfa, repeatStart, repeatEnd); |
| repeatEnd.addEpsilonTransition(repeatStart); |
| |
| repeatEnd.addEpsilonTransition(destination); |
| break; |
| } |
| } |
| } |
| |
| inline bool Term::isEndOfLineAssertion() const |
| { |
| return m_termType == TermType::CharacterSet && m_atomData.characterSet.bitCount() == 1 && m_atomData.characterSet.get(0); |
| } |
| |
| inline bool Term::matchesAtLeastOneCharacter() const |
| { |
| ASSERT(isValid()); |
| |
| if (m_quantifier == AtomQuantifier::ZeroOrOne || m_quantifier == AtomQuantifier::ZeroOrMore) |
| return false; |
| if (isEndOfLineAssertion()) |
| return false; |
| |
| if (m_termType == TermType::Group) { |
| for (const Term& term : m_atomData.group.terms) { |
| if (term.matchesAtLeastOneCharacter()) |
| return true; |
| } |
| return false; |
| } |
| return true; |
| } |
| |
| inline bool Term::isKnownToMatchAnyString() const |
| { |
| ASSERT(isValid()); |
| |
| switch (m_termType) { |
| case TermType::Empty: |
| ASSERT_NOT_REACHED(); |
| break; |
| case TermType::CharacterSet: |
| // ".*" is the only simple term matching any string. |
| return isUniversalTransition() && m_quantifier == AtomQuantifier::ZeroOrMore; |
| break; |
| case TermType::Group: { |
| // There are infinitely many ways to match anything with groups, we just handle simple cases |
| if (m_atomData.group.terms.size() != 1) |
| return false; |
| |
| const Term& firstTermInGroup = m_atomData.group.terms.first(); |
| // -(.*) with any quantifier. |
| if (firstTermInGroup.isKnownToMatchAnyString()) |
| return true; |
| |
| if (firstTermInGroup.isUniversalTransition()) { |
| // -(.)*, (.+)*, (.?)* etc. |
| if (m_quantifier == AtomQuantifier::ZeroOrMore) |
| return true; |
| |
| // -(.+)?. |
| if (m_quantifier == AtomQuantifier::ZeroOrOne && firstTermInGroup.m_quantifier == AtomQuantifier::OneOrMore) |
| return true; |
| |
| // -(.?)+. |
| if (m_quantifier == AtomQuantifier::OneOrMore && firstTermInGroup.m_quantifier == AtomQuantifier::ZeroOrOne) |
| return true; |
| } |
| break; |
| } |
| } |
| return false; |
| } |
| |
| inline bool Term::hasFixedLength() const |
| { |
| ASSERT(isValid()); |
| |
| switch (m_termType) { |
| case TermType::Empty: |
| ASSERT_NOT_REACHED(); |
| break; |
| case TermType::CharacterSet: |
| return m_quantifier == AtomQuantifier::One; |
| case TermType::Group: { |
| if (m_quantifier != AtomQuantifier::One) |
| return false; |
| for (const Term& term : m_atomData.group.terms) { |
| if (!term.hasFixedLength()) |
| return false; |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| inline Term& Term::operator=(const Term& other) |
| { |
| destroy(); |
| new (NotNull, this) Term(other); |
| return *this; |
| } |
| |
| inline Term& Term::operator=(Term&& other) |
| { |
| destroy(); |
| new (NotNull, this) Term(WTFMove(other)); |
| return *this; |
| } |
| |
| inline bool Term::operator==(const Term& other) const |
| { |
| if (other.m_termType != m_termType || other.m_quantifier != m_quantifier) |
| return false; |
| |
| switch (m_termType) { |
| case TermType::Empty: |
| return true; |
| case TermType::CharacterSet: |
| return m_atomData.characterSet == other.m_atomData.characterSet; |
| case TermType::Group: |
| return m_atomData.group == other.m_atomData.group; |
| } |
| ASSERT_NOT_REACHED(); |
| return false; |
| } |
| |
| inline unsigned Term::hash() const |
| { |
| unsigned primary = static_cast<unsigned>(m_termType) << 16 | static_cast<unsigned>(m_quantifier); |
| unsigned secondary = 0; |
| switch (m_termType) { |
| case TermType::Empty: |
| secondary = 52184393; |
| break; |
| case TermType::CharacterSet: |
| secondary = m_atomData.characterSet.hash(); |
| break; |
| case TermType::Group: |
| secondary = m_atomData.group.hash(); |
| break; |
| } |
| return WTF::pairIntHash(primary, secondary); |
| } |
| |
| inline bool Term::isEmptyValue() const |
| { |
| return m_termType == TermType::Empty; |
| } |
| |
| inline bool Term::isUniversalTransition() const |
| { |
| ASSERT(isValid()); |
| |
| switch (m_termType) { |
| case TermType::Empty: |
| ASSERT_NOT_REACHED(); |
| break; |
| case TermType::CharacterSet: |
| return (m_atomData.characterSet.inverted() && !m_atomData.characterSet.bitCount()) |
| || (!m_atomData.characterSet.inverted() && m_atomData.characterSet.bitCount() == 127 && !m_atomData.characterSet.get(0)); |
| case TermType::Group: |
| return m_atomData.group.terms.size() == 1 && m_atomData.group.terms.first().isUniversalTransition(); |
| } |
| return false; |
| } |
| |
| inline void Term::generateSubgraphForAtom(NFA& nfa, ImmutableCharNFANodeBuilder& source, const ImmutableCharNFANodeBuilder& destination) const |
| { |
| generateSubgraphForAtom(nfa, source, destination.nodeId()); |
| } |
| |
| inline void Term::generateSubgraphForAtom(NFA& nfa, ImmutableCharNFANodeBuilder& source, uint32_t destination) const |
| { |
| switch (m_termType) { |
| case TermType::Empty: |
| ASSERT_NOT_REACHED(); |
| source.addEpsilonTransition(destination); |
| break; |
| case TermType::CharacterSet: { |
| if (!m_atomData.characterSet.inverted()) { |
| UChar i = 0; |
| while (true) { |
| while (i < 128 && !m_atomData.characterSet.get(i)) |
| ++i; |
| if (i == 128) |
| break; |
| |
| UChar start = i; |
| ++i; |
| while (i < 128 && m_atomData.characterSet.get(i)) |
| ++i; |
| source.addTransition(start, i - 1, destination); |
| } |
| } else { |
| UChar i = 1; |
| while (true) { |
| while (i < 128 && m_atomData.characterSet.get(i)) |
| ++i; |
| if (i == 128) |
| break; |
| |
| UChar start = i; |
| ++i; |
| while (i < 128 && !m_atomData.characterSet.get(i)) |
| ++i; |
| source.addTransition(start, i - 1, destination); |
| } |
| } |
| break; |
| } |
| case TermType::Group: { |
| if (m_atomData.group.terms.isEmpty()) { |
| // FIXME: any kind of empty term could be avoided in the parser. This case should turned into an assertion. |
| source.addEpsilonTransition(destination); |
| return; |
| } |
| |
| if (m_atomData.group.terms.size() == 1) { |
| m_atomData.group.terms.first().generateGraph(nfa, source, destination); |
| return; |
| } |
| |
| ImmutableCharNFANodeBuilder lastTarget = m_atomData.group.terms.first().generateGraph(nfa, source, ActionList()); |
| for (unsigned i = 1; i < m_atomData.group.terms.size() - 1; ++i) { |
| const Term& currentTerm = m_atomData.group.terms[i]; |
| ImmutableCharNFANodeBuilder newNode = currentTerm.generateGraph(nfa, lastTarget, ActionList()); |
| lastTarget = WTFMove(newNode); |
| } |
| const Term& lastTerm = m_atomData.group.terms.last(); |
| lastTerm.generateGraph(nfa, lastTarget, destination); |
| break; |
| } |
| } |
| } |
| |
| inline void Term::destroy() |
| { |
| switch (m_termType) { |
| case TermType::Empty: |
| break; |
| case TermType::CharacterSet: |
| m_atomData.characterSet.~CharacterSet(); |
| break; |
| case TermType::Group: |
| m_atomData.group.~Group(); |
| break; |
| } |
| m_termType = TermType::Empty; |
| } |
| |
| #if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING |
| inline size_t Term::memoryUsed() const |
| { |
| size_t extraMemory = 0; |
| if (m_termType == TermType::Group) { |
| for (const Term& term : m_atomData.group.terms) |
| extraMemory += term.memoryUsed(); |
| } |
| return sizeof(Term) + extraMemory; |
| } |
| #endif |
| |
| } // namespace ContentExtensions |
| } // namespace WebCore |
| |
| #endif // ENABLE(CONTENT_EXTENSIONS) |