| /* |
| * Copyright (C) 1999-2001 Harri Porten (porten@kde.org) |
| * Copyright (C) 2001 Peter Kelly (pmk@post.com) |
| * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| * |
| */ |
| |
| #ifndef JSString_h |
| #define JSString_h |
| #include "CallFrame.h" |
| #include "CommonIdentifiers.h" |
| #include "Identifier.h" |
| #include "PropertyDescriptor.h" |
| #include "PropertySlot.h" |
| #include "Structure.h" |
| |
| namespace JSC { |
| |
| class JSString; |
| class JSRopeString; |
| class LLIntOffsetsExtractor; |
| |
| JSString* jsEmptyString(JSGlobalData*); |
| JSString* jsEmptyString(ExecState*); |
| JSString* jsString(JSGlobalData*, const String&); // returns empty string if passed null string |
| JSString* jsString(ExecState*, const String&); // returns empty string if passed null string |
| |
| JSString* jsSingleCharacterString(JSGlobalData*, UChar); |
| JSString* jsSingleCharacterString(ExecState*, UChar); |
| JSString* jsSingleCharacterSubstring(ExecState*, const String&, unsigned offset); |
| JSString* jsSubstring(JSGlobalData*, const String&, unsigned offset, unsigned length); |
| JSString* jsSubstring(ExecState*, const String&, unsigned offset, unsigned length); |
| |
| // Non-trivial strings are two or more characters long. |
| // These functions are faster than just calling jsString. |
| JSString* jsNontrivialString(JSGlobalData*, const String&); |
| JSString* jsNontrivialString(ExecState*, const String&); |
| |
| // Should be used for strings that are owned by an object that will |
| // likely outlive the JSValue this makes, such as the parse tree or a |
| // DOM object that contains a String |
| JSString* jsOwnedString(JSGlobalData*, const String&); |
| JSString* jsOwnedString(ExecState*, const String&); |
| |
| JSRopeString* jsStringBuilder(JSGlobalData*); |
| |
| class JSString : public JSCell { |
| public: |
| friend class JIT; |
| friend class JSGlobalData; |
| friend class SpecializedThunkJIT; |
| friend class JSRopeString; |
| friend class MarkStack; |
| friend class SlotVisitor; |
| friend struct ThunkHelpers; |
| |
| typedef JSCell Base; |
| |
| static const bool needsDestruction = true; |
| static const bool hasImmortalStructure = true; |
| static void destroy(JSCell*); |
| |
| private: |
| JSString(JSGlobalData& globalData, PassRefPtr<StringImpl> value) |
| : JSCell(globalData, globalData.stringStructure.get()) |
| , m_flags(0) |
| , m_value(value) |
| { |
| } |
| |
| JSString(JSGlobalData& globalData) |
| : JSCell(globalData, globalData.stringStructure.get()) |
| , m_flags(0) |
| { |
| } |
| |
| void finishCreation(JSGlobalData& globalData, size_t length) |
| { |
| ASSERT(!m_value.isNull()); |
| Base::finishCreation(globalData); |
| m_length = length; |
| setIs8Bit(m_value.impl()->is8Bit()); |
| globalData.m_newStringsSinceLastHashConst++; |
| } |
| |
| void finishCreation(JSGlobalData& globalData, size_t length, size_t cost) |
| { |
| ASSERT(!m_value.isNull()); |
| Base::finishCreation(globalData); |
| m_length = length; |
| setIs8Bit(m_value.impl()->is8Bit()); |
| Heap::heap(this)->reportExtraMemoryCost(cost); |
| globalData.m_newStringsSinceLastHashConst++; |
| } |
| |
| protected: |
| void finishCreation(JSGlobalData& globalData) |
| { |
| Base::finishCreation(globalData); |
| m_length = 0; |
| setIs8Bit(true); |
| globalData.m_newStringsSinceLastHashConst++; |
| } |
| |
| public: |
| static JSString* create(JSGlobalData& globalData, PassRefPtr<StringImpl> value) |
| { |
| ASSERT(value); |
| size_t length = value->length(); |
| size_t cost = value->cost(); |
| JSString* newString = new (NotNull, allocateCell<JSString>(globalData.heap)) JSString(globalData, value); |
| newString->finishCreation(globalData, length, cost); |
| return newString; |
| } |
| static JSString* createHasOtherOwner(JSGlobalData& globalData, PassRefPtr<StringImpl> value) |
| { |
| ASSERT(value); |
| size_t length = value->length(); |
| JSString* newString = new (NotNull, allocateCell<JSString>(globalData.heap)) JSString(globalData, value); |
| newString->finishCreation(globalData, length); |
| return newString; |
| } |
| |
| const String& value(ExecState*) const; |
| const String& tryGetValue() const; |
| unsigned length() { return m_length; } |
| |
| JSValue toPrimitive(ExecState*, PreferredPrimitiveType) const; |
| JS_EXPORT_PRIVATE bool toBoolean() const; |
| bool getPrimitiveNumber(ExecState*, double& number, JSValue&) const; |
| JSObject* toObject(ExecState*, JSGlobalObject*) const; |
| double toNumber(ExecState*) const; |
| |
| bool getStringPropertySlot(ExecState*, PropertyName, PropertySlot&); |
| bool getStringPropertySlot(ExecState*, unsigned propertyName, PropertySlot&); |
| bool getStringPropertyDescriptor(ExecState*, PropertyName, PropertyDescriptor&); |
| |
| bool canGetIndex(unsigned i) { return i < m_length; } |
| JSString* getIndex(ExecState*, unsigned); |
| |
| static Structure* createStructure(JSGlobalData& globalData, JSGlobalObject* globalObject, JSValue proto) |
| { |
| return Structure::create(globalData, globalObject, proto, TypeInfo(StringType, OverridesGetOwnPropertySlot | InterceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero), &s_info); |
| } |
| |
| static size_t offsetOfLength() { return OBJECT_OFFSETOF(JSString, m_length); } |
| static size_t offsetOfValue() { return OBJECT_OFFSETOF(JSString, m_value); } |
| |
| DECLARE_EXPORTED_CLASSINFO(); |
| |
| static void visitChildren(JSCell*, SlotVisitor&); |
| |
| protected: |
| bool isRope() const { return m_value.isNull(); } |
| bool is8Bit() const { return m_flags & Is8Bit; } |
| void setIs8Bit(bool flag) |
| { |
| if (flag) |
| m_flags |= Is8Bit; |
| else |
| m_flags &= ~Is8Bit; |
| } |
| bool shouldTryHashConst(); |
| bool isHashConstSingleton() const { return m_flags & IsHashConstSingleton; } |
| void clearHashConstSingleton() { m_flags &= ~IsHashConstSingleton; } |
| void setHashConstSingleton() { m_flags |= IsHashConstSingleton; } |
| bool tryHashConstLock(); |
| void releaseHashConstLock(); |
| |
| unsigned m_flags; |
| |
| enum { |
| HashConstLock = 1u << 2, |
| IsHashConstSingleton = 1u << 1, |
| Is8Bit = 1u |
| }; |
| |
| // A string is represented either by a String or a rope of fibers. |
| unsigned m_length; |
| mutable String m_value; |
| |
| private: |
| friend class LLIntOffsetsExtractor; |
| |
| static JSObject* toThisObject(JSCell*, ExecState*); |
| |
| // Actually getPropertySlot, not getOwnPropertySlot (see JSCell). |
| static bool getOwnPropertySlot(JSCell*, ExecState*, PropertyName, PropertySlot&); |
| static bool getOwnPropertySlotByIndex(JSCell*, ExecState*, unsigned propertyName, PropertySlot&); |
| |
| String& string() { ASSERT(!isRope()); return m_value; } |
| |
| friend JSValue jsString(ExecState*, JSString*, JSString*); |
| friend JSString* jsSubstring(ExecState*, JSString*, unsigned offset, unsigned length); |
| }; |
| |
| class JSRopeString : public JSString { |
| friend class JSString; |
| |
| friend JSRopeString* jsStringBuilder(JSGlobalData*); |
| |
| class RopeBuilder { |
| public: |
| RopeBuilder(JSGlobalData& globalData) |
| : m_globalData(globalData) |
| , m_jsString(jsStringBuilder(&globalData)) |
| , m_index(0) |
| { |
| } |
| |
| void append(JSString* jsString) |
| { |
| if (m_index == JSRopeString::s_maxInternalRopeLength) |
| expand(); |
| m_jsString->append(m_globalData, m_index++, jsString); |
| } |
| |
| JSRopeString* release() |
| { |
| JSRopeString* tmp = m_jsString; |
| m_jsString = 0; |
| return tmp; |
| } |
| |
| unsigned length() { return m_jsString->m_length; } |
| |
| private: |
| void expand(); |
| |
| JSGlobalData& m_globalData; |
| JSRopeString* m_jsString; |
| size_t m_index; |
| }; |
| |
| private: |
| JSRopeString(JSGlobalData& globalData) |
| : JSString(globalData) |
| { |
| } |
| |
| void finishCreation(JSGlobalData& globalData, JSString* s1, JSString* s2) |
| { |
| Base::finishCreation(globalData); |
| m_length = s1->length() + s2->length(); |
| setIs8Bit(s1->is8Bit() && s2->is8Bit()); |
| m_fibers[0].set(globalData, this, s1); |
| m_fibers[1].set(globalData, this, s2); |
| } |
| |
| void finishCreation(JSGlobalData& globalData, JSString* s1, JSString* s2, JSString* s3) |
| { |
| Base::finishCreation(globalData); |
| m_length = s1->length() + s2->length() + s3->length(); |
| setIs8Bit(s1->is8Bit() && s2->is8Bit() && s3->is8Bit()); |
| m_fibers[0].set(globalData, this, s1); |
| m_fibers[1].set(globalData, this, s2); |
| m_fibers[2].set(globalData, this, s3); |
| } |
| |
| void finishCreation(JSGlobalData& globalData) |
| { |
| JSString::finishCreation(globalData); |
| } |
| |
| void append(JSGlobalData& globalData, size_t index, JSString* jsString) |
| { |
| m_fibers[index].set(globalData, this, jsString); |
| m_length += jsString->m_length; |
| setIs8Bit(is8Bit() && jsString->is8Bit()); |
| } |
| |
| static JSRopeString* createNull(JSGlobalData& globalData) |
| { |
| JSRopeString* newString = new (NotNull, allocateCell<JSRopeString>(globalData.heap)) JSRopeString(globalData); |
| newString->finishCreation(globalData); |
| return newString; |
| } |
| |
| public: |
| static JSString* create(JSGlobalData& globalData, JSString* s1, JSString* s2) |
| { |
| JSRopeString* newString = new (NotNull, allocateCell<JSRopeString>(globalData.heap)) JSRopeString(globalData); |
| newString->finishCreation(globalData, s1, s2); |
| return newString; |
| } |
| static JSString* create(JSGlobalData& globalData, JSString* s1, JSString* s2, JSString* s3) |
| { |
| JSRopeString* newString = new (NotNull, allocateCell<JSRopeString>(globalData.heap)) JSRopeString(globalData); |
| newString->finishCreation(globalData, s1, s2, s3); |
| return newString; |
| } |
| |
| void visitFibers(SlotVisitor&); |
| |
| private: |
| friend JSValue jsString(ExecState*, Register*, unsigned); |
| friend JSValue jsStringFromArguments(ExecState*, JSValue); |
| |
| JS_EXPORT_PRIVATE void resolveRope(ExecState*) const; |
| void resolveRopeSlowCase8(LChar*) const; |
| void resolveRopeSlowCase(UChar*) const; |
| void outOfMemory(ExecState*) const; |
| |
| JSString* getIndexSlowCase(ExecState*, unsigned); |
| |
| static const unsigned s_maxInternalRopeLength = 3; |
| |
| mutable FixedArray<WriteBarrier<JSString>, s_maxInternalRopeLength> m_fibers; |
| }; |
| |
| JSString* asString(JSValue); |
| |
| inline JSString* asString(JSValue value) |
| { |
| ASSERT(value.asCell()->isString()); |
| return jsCast<JSString*>(value.asCell()); |
| } |
| |
| inline JSString* jsEmptyString(JSGlobalData* globalData) |
| { |
| return globalData->smallStrings.emptyString(globalData); |
| } |
| |
| ALWAYS_INLINE JSString* jsSingleCharacterString(JSGlobalData* globalData, UChar c) |
| { |
| if (c <= maxSingleCharacterString) |
| return globalData->smallStrings.singleCharacterString(globalData, c); |
| return JSString::create(*globalData, String(&c, 1).impl()); |
| } |
| |
| ALWAYS_INLINE JSString* jsSingleCharacterSubstring(ExecState* exec, const String& s, unsigned offset) |
| { |
| JSGlobalData* globalData = &exec->globalData(); |
| ASSERT(offset < static_cast<unsigned>(s.length())); |
| UChar c = s.characterAt(offset); |
| if (c <= maxSingleCharacterString) |
| return globalData->smallStrings.singleCharacterString(globalData, c); |
| return JSString::create(*globalData, StringImpl::create(s.impl(), offset, 1)); |
| } |
| |
| inline JSString* jsNontrivialString(JSGlobalData* globalData, const String& s) |
| { |
| ASSERT(s.length() > 1); |
| return JSString::create(*globalData, s.impl()); |
| } |
| |
| inline const String& JSString::value(ExecState* exec) const |
| { |
| if (isRope()) |
| static_cast<const JSRopeString*>(this)->resolveRope(exec); |
| return m_value; |
| } |
| |
| inline const String& JSString::tryGetValue() const |
| { |
| if (isRope()) |
| static_cast<const JSRopeString*>(this)->resolveRope(0); |
| return m_value; |
| } |
| |
| inline JSString* JSString::getIndex(ExecState* exec, unsigned i) |
| { |
| ASSERT(canGetIndex(i)); |
| if (isRope()) |
| return static_cast<JSRopeString*>(this)->getIndexSlowCase(exec, i); |
| ASSERT(i < m_value.length()); |
| return jsSingleCharacterSubstring(exec, m_value, i); |
| } |
| |
| inline JSString* jsString(JSGlobalData* globalData, const String& s) |
| { |
| int size = s.length(); |
| if (!size) |
| return globalData->smallStrings.emptyString(globalData); |
| if (size == 1) { |
| UChar c = s.characterAt(0); |
| if (c <= maxSingleCharacterString) |
| return globalData->smallStrings.singleCharacterString(globalData, c); |
| } |
| return JSString::create(*globalData, s.impl()); |
| } |
| |
| inline JSString* jsSubstring(ExecState* exec, JSString* s, unsigned offset, unsigned length) |
| { |
| ASSERT(offset <= static_cast<unsigned>(s->length())); |
| ASSERT(length <= static_cast<unsigned>(s->length())); |
| ASSERT(offset + length <= static_cast<unsigned>(s->length())); |
| JSGlobalData* globalData = &exec->globalData(); |
| if (!length) |
| return globalData->smallStrings.emptyString(globalData); |
| return jsSubstring(globalData, s->value(exec), offset, length); |
| } |
| |
| inline JSString* jsSubstring8(JSGlobalData* globalData, const String& s, unsigned offset, unsigned length) |
| { |
| ASSERT(offset <= static_cast<unsigned>(s.length())); |
| ASSERT(length <= static_cast<unsigned>(s.length())); |
| ASSERT(offset + length <= static_cast<unsigned>(s.length())); |
| if (!length) |
| return globalData->smallStrings.emptyString(globalData); |
| if (length == 1) { |
| UChar c = s.characterAt(offset); |
| if (c <= maxSingleCharacterString) |
| return globalData->smallStrings.singleCharacterString(globalData, c); |
| } |
| return JSString::createHasOtherOwner(*globalData, StringImpl::create8(s.impl(), offset, length)); |
| } |
| |
| inline JSString* jsSubstring(JSGlobalData* globalData, const String& s, unsigned offset, unsigned length) |
| { |
| ASSERT(offset <= static_cast<unsigned>(s.length())); |
| ASSERT(length <= static_cast<unsigned>(s.length())); |
| ASSERT(offset + length <= static_cast<unsigned>(s.length())); |
| if (!length) |
| return globalData->smallStrings.emptyString(globalData); |
| if (length == 1) { |
| UChar c = s.characterAt(offset); |
| if (c <= maxSingleCharacterString) |
| return globalData->smallStrings.singleCharacterString(globalData, c); |
| } |
| return JSString::createHasOtherOwner(*globalData, StringImpl::create(s.impl(), offset, length)); |
| } |
| |
| inline JSString* jsOwnedString(JSGlobalData* globalData, const String& s) |
| { |
| int size = s.length(); |
| if (!size) |
| return globalData->smallStrings.emptyString(globalData); |
| if (size == 1) { |
| UChar c = s.characterAt(0); |
| if (c <= maxSingleCharacterString) |
| return globalData->smallStrings.singleCharacterString(globalData, c); |
| } |
| return JSString::createHasOtherOwner(*globalData, s.impl()); |
| } |
| |
| inline JSRopeString* jsStringBuilder(JSGlobalData* globalData) |
| { |
| return JSRopeString::createNull(*globalData); |
| } |
| |
| inline JSString* jsEmptyString(ExecState* exec) { return jsEmptyString(&exec->globalData()); } |
| inline JSString* jsString(ExecState* exec, const String& s) { return jsString(&exec->globalData(), s); } |
| inline JSString* jsSingleCharacterString(ExecState* exec, UChar c) { return jsSingleCharacterString(&exec->globalData(), c); } |
| inline JSString* jsSubstring8(ExecState* exec, const String& s, unsigned offset, unsigned length) { return jsSubstring8(&exec->globalData(), s, offset, length); } |
| inline JSString* jsSubstring(ExecState* exec, const String& s, unsigned offset, unsigned length) { return jsSubstring(&exec->globalData(), s, offset, length); } |
| inline JSString* jsNontrivialString(ExecState* exec, const String& s) { return jsNontrivialString(&exec->globalData(), s); } |
| inline JSString* jsOwnedString(ExecState* exec, const String& s) { return jsOwnedString(&exec->globalData(), s); } |
| |
| ALWAYS_INLINE bool JSString::getStringPropertySlot(ExecState* exec, PropertyName propertyName, PropertySlot& slot) |
| { |
| if (propertyName == exec->propertyNames().length) { |
| slot.setValue(jsNumber(m_length)); |
| return true; |
| } |
| |
| unsigned i = propertyName.asIndex(); |
| if (i < m_length) { |
| ASSERT(i != PropertyName::NotAnIndex); // No need for an explicit check, the above test would always fail! |
| slot.setValue(getIndex(exec, i)); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| ALWAYS_INLINE bool JSString::getStringPropertySlot(ExecState* exec, unsigned propertyName, PropertySlot& slot) |
| { |
| if (propertyName < m_length) { |
| slot.setValue(getIndex(exec, propertyName)); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| inline bool isJSString(JSValue v) { return v.isCell() && v.asCell()->classInfo() == JSString::s_classinfo(); } |
| |
| inline bool JSCell::toBoolean(ExecState* exec) const |
| { |
| if (isString()) |
| return static_cast<const JSString*>(this)->toBoolean(); |
| return !structure()->masqueradesAsUndefined(exec->lexicalGlobalObject()); |
| } |
| |
| // --- JSValue inlines ---------------------------- |
| |
| inline bool JSValue::toBoolean(ExecState* exec) const |
| { |
| if (isInt32()) |
| return asInt32(); |
| if (isDouble()) |
| return asDouble() > 0.0 || asDouble() < 0.0; // false for NaN |
| if (isCell()) |
| return asCell()->toBoolean(exec); |
| return isTrue(); // false, null, and undefined all convert to false. |
| } |
| |
| inline JSString* JSValue::toString(ExecState* exec) const |
| { |
| if (isString()) |
| return jsCast<JSString*>(asCell()); |
| return toStringSlowCase(exec); |
| } |
| |
| inline String JSValue::toWTFString(ExecState* exec) const |
| { |
| if (isString()) |
| return static_cast<JSString*>(asCell())->value(exec); |
| return toWTFStringSlowCase(exec); |
| } |
| |
| ALWAYS_INLINE String inlineJSValueNotStringtoString(const JSValue& value, ExecState* exec) |
| { |
| JSGlobalData& globalData = exec->globalData(); |
| if (value.isInt32()) |
| return globalData.numericStrings.add(value.asInt32()); |
| if (value.isDouble()) |
| return globalData.numericStrings.add(value.asDouble()); |
| if (value.isTrue()) |
| return globalData.propertyNames->trueKeyword.string(); |
| if (value.isFalse()) |
| return globalData.propertyNames->falseKeyword.string(); |
| if (value.isNull()) |
| return globalData.propertyNames->nullKeyword.string(); |
| if (value.isUndefined()) |
| return globalData.propertyNames->undefinedKeyword.string(); |
| return value.toString(exec)->value(exec); |
| } |
| |
| ALWAYS_INLINE String JSValue::toWTFStringInline(ExecState* exec) const |
| { |
| if (isString()) |
| return static_cast<JSString*>(asCell())->value(exec); |
| |
| return inlineJSValueNotStringtoString(*this, exec); |
| } |
| |
| } // namespace JSC |
| |
| #endif // JSString_h |