Commit 35d680c7 authored by barraclough@apple.com's avatar barraclough@apple.com
Browse files

https://bugs.webkit.org/show_bug.cgi?id=32136

Add a rope representation to JSString.  Presently JSString always holds its data in UString form.
Instead, allow the result of a string concatenation to be represented in a tree form - with a
variable sized, reference-counted rope node retaining a set of UString::Reps (or other rope nopes).

Reviewed by Oliver "Brraaaaiiiinnnnnzzzzzzzz" Hunt.

Strings must still currently be resolved down to a flat UString representation before being used,
but by holding the string in a rope representation during construction we can avoid copying data
until we know the final size of the string.

~2% progression on SunSpider (~25% on date-format-xparb, ~20% on string-validate-input).

* JavaScriptCore.exp:

    - Update exports.

* interpreter/Interpreter.cpp:
(JSC::Interpreter::privateExecute):

    - Make use of new JSString::length() method to avoid prematurely resolving ropes.

* jit/JITOpcodes.cpp:
(JSC::JIT::privateCompileCTIMachineTrampolines):

    - Switch the string length trampoline to read the length directly from JSString::m_length,
      rather than from the JSString's UString::Rep's 'len' property.

* jit/JITStubs.cpp:
(JSC::DEFINE_STUB_FUNCTION):

    - Modify op_add such that addition of two strings, where either or both strings are already
      in rope representation, produces a rope as a result.

* runtime/JSString.cpp:
(JSC::JSString::Rope::~Rope):
(JSC::copyChars):
(JSC::JSString::resolveRope):
(JSC::JSString::getPrimitiveNumber):
(JSC::JSString::toBoolean):
(JSC::JSString::toNumber):
(JSC::JSString::toString):
(JSC::JSString::toThisString):
(JSC::JSString::getStringPropertyDescriptor):
* runtime/JSString.h:
(JSC::JSString::Rope::Fiber::Fiber):
(JSC::JSString::Rope::Fiber::destroy):
(JSC::JSString::Rope::Fiber::isRope):
(JSC::JSString::Rope::Fiber::rope):
(JSC::JSString::Rope::Fiber::string):
(JSC::JSString::Rope::create):
(JSC::JSString::Rope::initializeFiber):
(JSC::JSString::Rope::ropeLength):
(JSC::JSString::Rope::stringLength):
(JSC::JSString::Rope::fibers):
(JSC::JSString::Rope::Rope):
(JSC::JSString::Rope::operator new):
(JSC::JSString::JSString):
(JSC::JSString::value):
(JSC::JSString::length):
(JSC::JSString::isRope):
(JSC::JSString::rope):
(JSC::JSString::string):
(JSC::JSString::canGetIndex):
(JSC::jsSingleCharacterSubstring):
(JSC::JSString::getIndex):
(JSC::jsSubstring):
(JSC::JSString::getStringPropertySlot):

    - Add rope form.

* runtime/Operations.h:
(JSC::jsAdd):
(JSC::concatenateStrings):

    - Update string concatenation, and addition of ropes, to produce ropes.

* runtime/StringObject.cpp:
(JSC::StringObject::getOwnPropertyNames):

    - Make use of new JSString::length() method to avoid prematurely resolving ropes.



git-svn-id: http://svn.webkit.org/repository/webkit/trunk@51671 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent f63022af
2009-12-03 Gavin Barraclough <barraclough@apple.com>
Reviewed by Oliver "Brraaaaiiiinnnnnzzzzzzzz" Hunt.
https://bugs.webkit.org/show_bug.cgi?id=32136
Add a rope representation to JSString. Presently JSString always holds its data in UString form.
Instead, allow the result of a string concatenation to be represented in a tree form - with a
variable sized, reference-counted rope node retaining a set of UString::Reps (or other rope nopes).
Strings must still currently be resolved down to a flat UString representation before being used,
but by holding the string in a rope representation during construction we can avoid copying data
until we know the final size of the string.
~2% progression on SunSpider (~25% on date-format-xparb, ~20% on string-validate-input).
* JavaScriptCore.exp:
- Update exports.
* interpreter/Interpreter.cpp:
(JSC::Interpreter::privateExecute):
- Make use of new JSString::length() method to avoid prematurely resolving ropes.
* jit/JITOpcodes.cpp:
(JSC::JIT::privateCompileCTIMachineTrampolines):
- Switch the string length trampoline to read the length directly from JSString::m_length,
rather than from the JSString's UString::Rep's 'len' property.
* jit/JITStubs.cpp:
(JSC::DEFINE_STUB_FUNCTION):
- Modify op_add such that addition of two strings, where either or both strings are already
in rope representation, produces a rope as a result.
* runtime/JSString.cpp:
(JSC::JSString::Rope::~Rope):
(JSC::copyChars):
(JSC::JSString::resolveRope):
(JSC::JSString::getPrimitiveNumber):
(JSC::JSString::toBoolean):
(JSC::JSString::toNumber):
(JSC::JSString::toString):
(JSC::JSString::toThisString):
(JSC::JSString::getStringPropertyDescriptor):
* runtime/JSString.h:
(JSC::JSString::Rope::Fiber::Fiber):
(JSC::JSString::Rope::Fiber::destroy):
(JSC::JSString::Rope::Fiber::isRope):
(JSC::JSString::Rope::Fiber::rope):
(JSC::JSString::Rope::Fiber::string):
(JSC::JSString::Rope::create):
(JSC::JSString::Rope::initializeFiber):
(JSC::JSString::Rope::ropeLength):
(JSC::JSString::Rope::stringLength):
(JSC::JSString::Rope::fibers):
(JSC::JSString::Rope::Rope):
(JSC::JSString::Rope::operator new):
(JSC::JSString::JSString):
(JSC::JSString::value):
(JSC::JSString::length):
(JSC::JSString::isRope):
(JSC::JSString::rope):
(JSC::JSString::string):
(JSC::JSString::canGetIndex):
(JSC::jsSingleCharacterSubstring):
(JSC::JSString::getIndex):
(JSC::jsSubstring):
(JSC::JSString::getStringPropertySlot):
- Add rope form.
* runtime/Operations.h:
(JSC::jsAdd):
(JSC::concatenateStrings):
- Update string concatenation, and addition of ropes, to produce ropes.
* runtime/StringObject.cpp:
(JSC::StringObject::getOwnPropertyNames):
- Make use of new JSString::length() method to avoid prematurely resolving ropes.
2009-11-23 Jeremy Moskovich <jeremy@chromium.org>
Reviewed by Eric Seidel.
......
......@@ -402,6 +402,7 @@ __ZNK3JSC8JSObject8toObjectEPNS_9ExecStateE
__ZNK3JSC8JSObject8toStringEPNS_9ExecStateE
__ZNK3JSC8JSObject9classNameEv
__ZNK3JSC8JSObject9toBooleanEPNS_9ExecStateE
__ZNK3JSC8JSString11resolveRopeEv
__ZNK3JSC9HashTable11createTableEPNS_12JSGlobalDataE
__ZNK3JSC9HashTable11deleteTableEv
__ZNK3WTF8Collator7collateEPKtmS2_m
......
......@@ -2265,7 +2265,7 @@ JSValue Interpreter::privateExecute(ExecutionFlag flag, RegisterFile* registerFi
JSValue baseValue = callFrame->r(base).jsValue();
if (LIKELY(isJSString(globalData, baseValue))) {
int dst = vPC[1].u.operand;
callFrame->r(dst) = jsNumber(callFrame, asString(baseValue)->value().size());
callFrame->r(dst) = jsNumber(callFrame, asString(baseValue)->length());
vPC += OPCODE_LENGTH(op_get_string_length);
NEXT_INSTRUCTION();
}
......
......@@ -52,8 +52,7 @@ void JIT::privateCompileCTIMachineTrampolines(RefPtr<ExecutablePool>* executable
Jump string_failureCases2 = branchPtr(NotEqual, Address(regT0), ImmPtr(m_globalData->jsStringVPtr));
// Checks out okay! - get the length from the Ustring.
loadPtr(Address(regT0, OBJECT_OFFSETOF(JSString, m_value) + OBJECT_OFFSETOF(UString, m_rep)), regT2);
load32(Address(regT2, OBJECT_OFFSETOF(UString::Rep, len)), regT2);
load32(Address(regT0, OBJECT_OFFSETOF(JSString, m_length)), regT2);
Jump string_failureCases3 = branch32(Above, regT2, Imm32(INT_MAX));
move(regT2, regT0);
......@@ -1593,8 +1592,7 @@ void JIT::privateCompileCTIMachineTrampolines(RefPtr<ExecutablePool>* executable
Jump string_failureCases2 = branchPtr(NotEqual, Address(regT0), ImmPtr(m_globalData->jsStringVPtr));
// Checks out okay! - get the length from the Ustring.
loadPtr(Address(regT0, OBJECT_OFFSETOF(JSString, m_value) + OBJECT_OFFSETOF(UString, m_rep)), regT0);
load32(Address(regT0, OBJECT_OFFSETOF(UString::Rep, len)), regT0);
load32(Address(regT0, OBJECT_OFFSETOF(JSString, m_length)), regT0);
Jump string_failureCases3 = branch32(Above, regT0, Imm32(JSImmediate::maxImmediateInt));
......
......@@ -1043,6 +1043,14 @@ DEFINE_STUB_FUNCTION(EncodedJSValue, op_add)
bool leftIsString = v1.isString();
if (leftIsString && v2.isString()) {
if (asString(v1)->isRope() || asString(v2)->isRope()) {
RefPtr<JSString::Rope> rope = JSString::Rope::create(2);
rope->initializeFiber(0, asString(v1));
rope->initializeFiber(1, asString(v2));
JSGlobalData* globalData = &callFrame->globalData();
return JSValue::encode(new (globalData) JSString(globalData, rope.release()));
}
RefPtr<UString::Rep> value = concatenate(asString(v1)->value().rep(), asString(v2)->value().rep());
if (UNLIKELY(!value)) {
throwOutOfMemoryError(callFrame);
......
......@@ -30,36 +30,121 @@
namespace JSC {
JSString::Rope::~Rope()
{
for (unsigned i = 0; i < m_ropeLength; ++i) {
Fiber& fiber = m_fibers[i];
if (fiber.isRope())
fiber.rope()->deref();
else
fiber.string()->deref();
}
}
#define ROPE_COPY_CHARS_INLINE_CUTOFF 20
static inline void copyChars(UChar* destination, const UChar* source, unsigned numCharacters)
{
#ifdef ROPE_COPY_CHARS_INLINE_CUTOFF
if (numCharacters <= ROPE_COPY_CHARS_INLINE_CUTOFF) {
for (unsigned i = 0; i < numCharacters; ++i)
destination[i] = source[i];
return;
}
#endif
memcpy(destination, source, numCharacters * sizeof(UChar));
}
// Overview: this methods converts a JSString from holding a string in rope form
// down to a simple UString representation. It does so by building up the string
// backwards, since we want to avoid recursion, we expect that the tree structure
// representing the rope is likely imbalanced with more nodes down the left side
// (since appending to the string is likely more common) - and as such resolving
// in this fashion should minimize work queue size. (If we built the queue forwards
// we would likely have to place all of the constituent UString::Reps into the
// Vector before performing any concatenation, but by working backwards we likely
// only fill the queue with the number of substrings at any given level in a
// rope-of-ropes.)
void JSString::resolveRope() const
{
ASSERT(isRope());
// Allocate the buffer to hold the final string, position initially points to the end.
UChar* buffer = static_cast<UChar*>(fastMalloc(m_length * sizeof(UChar)));
UChar* position = buffer + m_length;
// Start with the current Rope.
Vector<Rope::Fiber, 32> workQueue;
Rope* rope = m_rope.get();
while (true) {
// Copy the contents of the current rope into the workQueue, with the last item in 'currentFiber'
// (we will be working backwards over the rope).
unsigned ropeLengthMinusOne = rope->ropeLength() - 1;
for (unsigned i = 0; i < ropeLengthMinusOne; ++i)
workQueue.append(rope->fibers(i));
Rope::Fiber currentFiber = rope->fibers(ropeLengthMinusOne);
// Spin backwards over the workQueue (starting with currentFiber),
// writing the strings into the buffer.
while (currentFiber.isString()) {
UString::Rep* string = currentFiber.string();
unsigned length = string->len;
position -= length;
copyChars(position, string->data(), length);
// Was this the last item in the work queue?
if (workQueue.isEmpty())
goto breakOutOfTwoLoops;
// No! - set the next item up to process.
currentFiber = workQueue.last();
workQueue.removeLast();
}
// If we get here we fell out of the loop concatenating strings - currentFiber is a rope.
// set the 'rope' variable, and continue around the loop.
ASSERT(currentFiber.isRope());
rope = currentFiber.rope();
}
breakOutOfTwoLoops:
// Create a string from the UChar buffer, clear the rope RefPtr.
ASSERT(buffer == position);
m_value = UString::Rep::create(buffer, m_length, false);
m_rope.clear();
ASSERT(!isRope());
}
JSValue JSString::toPrimitive(ExecState*, PreferredPrimitiveType) const
{
return const_cast<JSString*>(this);
}
bool JSString::getPrimitiveNumber(ExecState*, double& number, JSValue& value)
bool JSString::getPrimitiveNumber(ExecState*, double& number, JSValue& result)
{
value = this;
number = m_value.toDouble();
result = this;
number = value().toDouble();
return false;
}
bool JSString::toBoolean(ExecState*) const
{
return !m_value.isEmpty();
return m_length;
}
double JSString::toNumber(ExecState*) const
{
return m_value.toDouble();
return value().toDouble();
}
UString JSString::toString(ExecState*) const
{
return m_value;
return value();
}
UString JSString::toThisString(ExecState*) const
{
return m_value;
return value();
}
JSString* JSString::toThisJSString(ExecState*)
......@@ -106,14 +191,14 @@ bool JSString::getOwnPropertySlot(ExecState* exec, const Identifier& propertyNam
bool JSString::getStringPropertyDescriptor(ExecState* exec, const Identifier& propertyName, PropertyDescriptor& descriptor)
{
if (propertyName == exec->propertyNames().length) {
descriptor.setDescriptor(jsNumber(exec, m_value.size()), DontEnum | DontDelete | ReadOnly);
descriptor.setDescriptor(jsNumber(exec, m_length), DontEnum | DontDelete | ReadOnly);
return true;
}
bool isStrictUInt32;
unsigned i = propertyName.toStrictUInt32(&isStrictUInt32);
if (isStrictUInt32 && i < static_cast<unsigned>(m_value.size())) {
descriptor.setDescriptor(jsSingleCharacterSubstring(exec, m_value, i), DontDelete | ReadOnly);
if (isStrictUInt32 && i < m_length) {
descriptor.setDescriptor(jsSingleCharacterSubstring(exec, value(), i), DontDelete | ReadOnly);
return true;
}
......
......@@ -60,12 +60,72 @@ namespace JSC {
JSString* jsOwnedString(ExecState*, const UString&);
class JSString : public JSCell {
public:
friend class JIT;
friend struct VPtrSet;
public:
// A Rope is a string composed of a set of substrings.
class Rope : public RefCounted<Rope> {
public:
// A Rope is composed from a set of smaller strings called Fibers.
// Each Fiber in a rope is either UString::Rep or another Rope.
class Fiber {
public:
Fiber() {}
Fiber(UString::Rep* string) : m_value(reinterpret_cast<intptr_t>(string)) {}
Fiber(Rope* rope) : m_value(reinterpret_cast<intptr_t>(rope) | 1) {}
bool isRope() { return m_value & 1; }
Rope* rope() { return reinterpret_cast<Rope*>(m_value & ~1); }
bool isString() { return !isRope(); }
UString::Rep* string() { return reinterpret_cast<UString::Rep*>(m_value); }
private:
intptr_t m_value;
};
// Creates a Rope comprising of 'ropeLength' Fibers.
// The Rope is constructed in an uninitialized state - initialize must be called for each Fiber in the Rope.
static PassRefPtr<Rope> create(unsigned ropeLength) { return adoptRef(new (ropeLength) Rope(ropeLength)); }
~Rope();
void initializeFiber(unsigned index, UString::Rep* string)
{
string->ref();
m_fibers[index] = Fiber(string);
m_stringLength += string->len;
}
void initializeFiber(unsigned index, Rope* rope)
{
rope->ref();
m_fibers[index] = Fiber(rope);
m_stringLength += rope->stringLength();
}
void initializeFiber(unsigned index, JSString* jsString)
{
if (jsString->isRope())
initializeFiber(index, jsString->rope());
else
initializeFiber(index, jsString->string().rep());
}
unsigned ropeLength() { return m_ropeLength; }
unsigned stringLength() { return m_stringLength; }
Fiber& fibers(unsigned index) { return m_fibers[index]; }
private:
Rope(unsigned ropeLength) : m_ropeLength(ropeLength), m_stringLength(0) {}
void* operator new(size_t, unsigned ropeLength) { return fastMalloc(sizeof(Rope) + (ropeLength - 1) * sizeof(UString::Rep*)); }
unsigned m_ropeLength;
unsigned m_stringLength;
Fiber m_fibers[1];
};
JSString(JSGlobalData* globalData, const UString& value)
: JSCell(globalData->stringStructure.get())
, m_length(value.size())
, m_value(value)
{
Heap::heap(this)->reportExtraMemoryCost(value.cost());
......@@ -74,22 +134,40 @@ namespace JSC {
enum HasOtherOwnerType { HasOtherOwner };
JSString(JSGlobalData* globalData, const UString& value, HasOtherOwnerType)
: JSCell(globalData->stringStructure.get())
, m_length(value.size())
, m_value(value)
{
}
JSString(JSGlobalData* globalData, PassRefPtr<UString::Rep> value, HasOtherOwnerType)
: JSCell(globalData->stringStructure.get())
, m_length(value->size())
, m_value(value)
{
}
JSString(JSGlobalData* globalData, PassRefPtr<JSString::Rope> rope)
: JSCell(globalData->stringStructure.get())
, m_length(rope->stringLength())
, m_rope(rope)
{
}
const UString& value() const { return m_value; }
const UString& value() const
{
if (m_rope)
resolveRope();
return m_value;
}
unsigned length() { return m_length; }
bool isRope() const { return m_rope; }
Rope* rope() { ASSERT(isRope()); return m_rope.get(); }
UString& string() { ASSERT(!isRope()); return m_value; }
bool getStringPropertySlot(ExecState*, const Identifier& propertyName, PropertySlot&);
bool getStringPropertySlot(ExecState*, unsigned propertyName, PropertySlot&);
bool getStringPropertyDescriptor(ExecState*, const Identifier& propertyName, PropertyDescriptor&);
bool canGetIndex(unsigned i) { return i < static_cast<unsigned>(m_value.size()); }
bool canGetIndex(unsigned i) { return i < m_length; }
JSString* getIndex(JSGlobalData*, unsigned);
static PassRefPtr<Structure> createStructure(JSValue proto) { return Structure::create(proto, TypeInfo(StringType, OverridesGetOwnPropertySlot | NeedsThisConversion)); }
......@@ -101,6 +179,8 @@ namespace JSC {
{
}
void resolveRope() const;
virtual JSValue toPrimitive(ExecState*, PreferredPrimitiveType) const;
virtual bool getPrimitiveNumber(ExecState*, double& number, JSValue& value);
virtual bool toBoolean(ExecState*) const;
......@@ -117,7 +197,10 @@ namespace JSC {
virtual bool getOwnPropertySlot(ExecState*, unsigned propertyName, PropertySlot&);
virtual bool getOwnPropertyDescriptor(ExecState*, const Identifier&, PropertyDescriptor&);
UString m_value;
// A string is represented either by a UString or a Rope.
unsigned m_length;
mutable UString m_value;
mutable RefPtr<Rope> m_rope;
};
JSString* asString(JSValue);
......@@ -146,7 +229,7 @@ namespace JSC {
UChar c = s.data()[offset];
if (c <= 0xFF)
return globalData->smallStrings.singleCharacterString(globalData, c);
return new (globalData) JSString(globalData, UString::Rep::create(s.rep(), offset, 1));
return new (globalData) JSString(globalData, UString(UString::Rep::create(s.rep(), offset, 1)));
}
inline JSString* jsNontrivialString(JSGlobalData* globalData, const char* s)
......@@ -166,7 +249,7 @@ namespace JSC {
inline JSString* JSString::getIndex(JSGlobalData* globalData, unsigned i)
{
ASSERT(canGetIndex(i));
return jsSingleCharacterSubstring(globalData, m_value, i);
return jsSingleCharacterSubstring(globalData, value(), i);
}
inline JSString* jsString(JSGlobalData* globalData, const UString& s)
......@@ -194,7 +277,7 @@ namespace JSC {
if (c <= 0xFF)
return globalData->smallStrings.singleCharacterString(globalData, c);
}
return new (globalData) JSString(globalData, UString::Rep::create(s.rep(), offset, length));
return new (globalData) JSString(globalData, UString(UString::Rep::create(s.rep(), offset, length)));
}
inline JSString* jsOwnedString(JSGlobalData* globalData, const UString& s)
......@@ -222,14 +305,14 @@ namespace JSC {
ALWAYS_INLINE bool JSString::getStringPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot& slot)
{
if (propertyName == exec->propertyNames().length) {
slot.setValue(jsNumber(exec, m_value.size()));
slot.setValue(jsNumber(exec, m_length));
return true;
}
bool isStrictUInt32;
unsigned i = propertyName.toStrictUInt32(&isStrictUInt32);
if (isStrictUInt32 && i < static_cast<unsigned>(m_value.size())) {
slot.setValue(jsSingleCharacterSubstring(exec, m_value, i));
if (isStrictUInt32 && i < m_length) {
slot.setValue(jsSingleCharacterSubstring(exec, value(), i));
return true;
}
......@@ -238,8 +321,8 @@ namespace JSC {
ALWAYS_INLINE bool JSString::getStringPropertySlot(ExecState* exec, unsigned propertyName, PropertySlot& slot)
{
if (propertyName < static_cast<unsigned>(m_value.size())) {
slot.setValue(jsSingleCharacterSubstring(exec, m_value, propertyName));
if (propertyName < m_length) {
slot.setValue(jsSingleCharacterSubstring(exec, value(), propertyName));
return true;
}
......
......@@ -204,6 +204,14 @@ namespace JSC {
bool leftIsString = v1.isString();
if (leftIsString && v2.isString()) {
if (asString(v1)->isRope() || asString(v2)->isRope()) {
RefPtr<JSString::Rope> rope = JSString::Rope::create(2);
rope->initializeFiber(0, asString(v1));
rope->initializeFiber(1, asString(v2));
JSGlobalData* globalData = &callFrame->globalData();
return new (globalData) JSString(globalData, rope.release());
}
RefPtr<UString::Rep> value = concatenate(asString(v1)->value().rep(), asString(v2)->value().rep());
if (!value)
return throwOutOfMemoryError(callFrame);
......@@ -298,47 +306,19 @@ namespace JSC {
{
ASSERT(count >= 3);
// Estimate the amount of space required to hold the entire string. If all
// arguments are strings, we can easily calculate the exact amount of space
// required. For any other arguments, for now let's assume they may require
// 11 UChars of storage. This is enouch to hold any int, and likely is also
// reasonable for the other immediates. We may want to come back and tune
// this value at some point.
unsigned bufferSize = 0;
for (unsigned i = 0; i < count; ++i) {
JSValue v = strings[i].jsValue();
if (LIKELY(v.isString()))
bufferSize += asString(v)->value().size();
else
bufferSize += 11;
}
RefPtr<JSString::Rope> rope = JSString::Rope::create(count);
// Allocate an output string to store the result.
// If the first argument is a String, and if it has the capacity (or can grow
// its capacity) to hold the entire result then use this as a base to concatenate
// onto. Otherwise, allocate a new empty output buffer.
JSValue firstValue = strings[0].jsValue();
RefPtr<UString::Rep> resultRep;
if (firstValue.isString() && (resultRep = asString(firstValue)->value().rep())->reserveCapacity(bufferSize)) {
// We're going to concatenate onto the first string - remove it from the list of items to be appended.
++strings;
--count;
} else
resultRep = UString::Rep::createEmptyBuffer(bufferSize);
UString result(resultRep);
// Loop over the operands, writing them into the output buffer.
for (unsigned i = 0; i < count; ++i) {
JSValue v = strings[i].jsValue();
if (LIKELY(v.isString()))
result.append(asString(v)->value());
rope->initializeFiber(i, asString(v));
else
result.append(v.toString(callFrame));
rope->initializeFiber(i, v.toString(callFrame).rep());
}
return jsString(callFrame, result);
JSGlobalData* globalData = &callFrame->globalData();
return new (globalData) JSString(globalData, rope.release());
}
} // namespace JSC
#endif // Operations_h
......@@ -84,7 +84,7 @@ bool StringObject::deleteProperty(ExecState* exec, const Identifier& propertyNam
void StringObject::getOwnPropertyNames(ExecState* exec, PropertyNameArray& propertyNames)
{
int size = internalValue()->value().size();
int size = internalValue()->length();
for (int i = 0; i < size; ++i)
propertyNames.add(Identifier(exec, UString::from(i)));
return JSObject::getOwnPropertyNames(exec, propertyNames);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment