Commit 8dfb5c65 authored by ap@webkit.org's avatar ap@webkit.org

Reviewed by Darin.

        <rdar://problem/5908520> REGRESSION (3.1.1-r33033): Crash in WebKit when opening or
        refreshing page on people.com

        The problem was that STL algorithms do not work with non-conformant comparators, and the
        site used sort(function() { return 0.5 - Math.random(); } to randomly shuffle an array.

        https://bugs.webkit.org/show_bug.cgi?id=18687
        REGRESSION(r32220): ecma/Array/15.4.4.5-3.js test now fails in GMT(BST)

        Besides relying on sort stability, this test was just broken, and kept failing with the
        new stable sort.

        Tests: fast/js/sort-randomly.html
               fast/js/sort-stability.html
               fast/js/comparefn-sort-stability.html

        * kjs/avl_tree.h: Added an AVL tree implementation.

        * JavaScriptCore.xcodeproj/project.pbxproj:
        * wtf/AVLTree.h: Added.
        Added an AVL tree implementation.

        * kjs/array_instance.cpp:
        (KJS::ArrayInstance::increaseVectorLength):
        (KJS::ArrayInstance::sort):
        (KJS::AVLTreeAbstractorForArrayCompare::get_less):
        (KJS::AVLTreeAbstractorForArrayCompare::set_less):
        (KJS::AVLTreeAbstractorForArrayCompare::get_greater):
        (KJS::AVLTreeAbstractorForArrayCompare::set_greater):
        (KJS::AVLTreeAbstractorForArrayCompare::get_balance_factor):
        (KJS::AVLTreeAbstractorForArrayCompare::set_balance_factor):
        (KJS::AVLTreeAbstractorForArrayCompare::compare_key_key):
        (KJS::AVLTreeAbstractorForArrayCompare::compare_key_node):
        (KJS::AVLTreeAbstractorForArrayCompare::compare_node_node):
        (KJS::AVLTreeAbstractorForArrayCompare::null):
        (KJS::ArrayInstance::compactForSorting):
        
        * kjs/array_instance.h: increaseVectorLength() now returns a bool to indicate whether it was
        successful.

        * wtf/Vector.h:
        (WTF::Vector::Vector):
        (WTF::::operator=):
        (WTF::::fill):
        Make these methods fail instead instead of crash when allocation fails, matching resize() and
        reserveCapacity(), which already had this behavior. Callers need to check for null buffer
        after making any Vector call that can try to allocate.

        * tests/mozilla/ecma/Array/15.4.4.5-3.js: Fixed the test to use a consistent sort function,
        as suggested in comments to a Mozilla bug filed about it (I'll keep tracking the bug to see
        what the final resolution is).



git-svn-id: http://svn.webkit.org/repository/webkit/trunk@33967 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 48a35d0d
2008-05-21 Alexey Proskuryakov <ap@webkit.org>
Reviewed by Darin.
<rdar://problem/5908520> REGRESSION (3.1.1-r33033): Crash in WebKit when opening or
refreshing page on people.com
The problem was that STL algorithms do not work with non-conformant comparators, and the
site used sort(function() { return 0.5 - Math.random(); } to randomly shuffle an array.
https://bugs.webkit.org/show_bug.cgi?id=18687
REGRESSION(r32220): ecma/Array/15.4.4.5-3.js test now fails in GMT(BST)
Besides relying on sort stability, this test was just broken, and kept failing with the
new stable sort.
Tests: fast/js/sort-randomly.html
fast/js/sort-stability.html
fast/js/comparefn-sort-stability.html
* kjs/avl_tree.h: Added an AVL tree implementation.
* JavaScriptCore.xcodeproj/project.pbxproj:
* wtf/AVLTree.h: Added.
Added an AVL tree implementation.
* kjs/array_instance.cpp:
(KJS::ArrayInstance::increaseVectorLength):
(KJS::ArrayInstance::sort):
(KJS::AVLTreeAbstractorForArrayCompare::get_less):
(KJS::AVLTreeAbstractorForArrayCompare::set_less):
(KJS::AVLTreeAbstractorForArrayCompare::get_greater):
(KJS::AVLTreeAbstractorForArrayCompare::set_greater):
(KJS::AVLTreeAbstractorForArrayCompare::get_balance_factor):
(KJS::AVLTreeAbstractorForArrayCompare::set_balance_factor):
(KJS::AVLTreeAbstractorForArrayCompare::compare_key_key):
(KJS::AVLTreeAbstractorForArrayCompare::compare_key_node):
(KJS::AVLTreeAbstractorForArrayCompare::compare_node_node):
(KJS::AVLTreeAbstractorForArrayCompare::null):
(KJS::ArrayInstance::compactForSorting):
* kjs/array_instance.h: increaseVectorLength() now returns a bool to indicate whether it was
successful.
* wtf/Vector.h:
(WTF::Vector::Vector):
(WTF::::operator=):
(WTF::::fill):
Make these methods fail instead of crash when allocation fails, matching resize() and
reserveCapacity(), which already had this behavior. Callers need to check for null buffer
after making any Vector call that can try to allocate.
* tests/mozilla/ecma/Array/15.4.4.5-3.js: Fixed the test to use a consistent sort function,
as suggested in comments to a Mozilla bug filed about it (I'll keep tracking the bug to see
what the final resolution is).
2008-05-20 Kevin McCullough <kmccullough@apple.com>
Reviewed by Tim.
......
......@@ -203,6 +203,7 @@
E178636D0D9BEEC300D74E75 /* InitializeThreading.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E178636C0D9BEEC300D74E75 /* InitializeThreading.cpp */; };
E195679609E7CF1200B89D13 /* UnicodeIcu.h in Headers */ = {isa = PBXBuildFile; fileRef = E195678F09E7CF1200B89D13 /* UnicodeIcu.h */; settings = {ATTRIBUTES = (Private, ); }; };
E195679809E7CF1200B89D13 /* Unicode.h in Headers */ = {isa = PBXBuildFile; fileRef = E195679409E7CF1200B89D13 /* Unicode.h */; settings = {ATTRIBUTES = (Private, ); }; };
E1A596380DE3E1C300C17E37 /* AVLTree.h in Headers */ = {isa = PBXBuildFile; fileRef = E1A596370DE3E1C300C17E37 /* AVLTree.h */; };
E1A862A90D7EBB76001EC6AA /* CollatorICU.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E1A862A80D7EBB76001EC6AA /* CollatorICU.cpp */; settings = {COMPILER_FLAGS = "-fno-strict-aliasing"; }; };
E1A862AB0D7EBB7D001EC6AA /* Collator.h in Headers */ = {isa = PBXBuildFile; fileRef = E1A862AA0D7EBB7D001EC6AA /* Collator.h */; settings = {ATTRIBUTES = (Private, ); }; };
E1A862D60D7F2B5C001EC6AA /* CollatorDefault.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E1A862D50D7F2B5C001EC6AA /* CollatorDefault.cpp */; };
......@@ -516,6 +517,7 @@
E178636C0D9BEEC300D74E75 /* InitializeThreading.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InitializeThreading.cpp; sourceTree = "<group>"; };
E195678F09E7CF1200B89D13 /* UnicodeIcu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UnicodeIcu.h; sourceTree = "<group>"; };
E195679409E7CF1200B89D13 /* Unicode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Unicode.h; sourceTree = "<group>"; };
E1A596370DE3E1C300C17E37 /* AVLTree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AVLTree.h; sourceTree = "<group>"; };
E1A862A80D7EBB76001EC6AA /* CollatorICU.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CollatorICU.cpp; sourceTree = "<group>"; };
E1A862AA0D7EBB7D001EC6AA /* Collator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Collator.h; sourceTree = "<group>"; };
E1A862D50D7F2B5C001EC6AA /* CollatorDefault.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CollatorDefault.cpp; sourceTree = "<group>"; };
......@@ -785,6 +787,7 @@
938C4F690CA06BC700D9310A /* ASCIICType.h */,
65E217B808E7EECC0023E5F6 /* Assertions.cpp */,
65E217B708E7EECC0023E5F6 /* Assertions.h */,
E1A596370DE3E1C300C17E37 /* AVLTree.h */,
5186111D0CC824830081412B /* Deque.h */,
938C4F6B0CA06BCE00D9310A /* DisallowCType.h */,
65E217B908E7EECC0023E5F6 /* FastMalloc.cpp */,
......@@ -1155,6 +1158,7 @@
958D85830DC93230008ABF27 /* StrHash.h in Headers */,
95742F660DD11F5A000917FB /* Profile.h in Headers */,
5DE3D0F50DD8DDFB00468714 /* WebKitAvailability.h in Headers */,
E1A596380DE3E1C300C17E37 /* AVLTree.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......
......@@ -25,6 +25,7 @@
#include "PropertyNameArray.h"
#include <wtf/Assertions.h>
#include <wtf/AVLTree.h>
using std::min;
......@@ -48,8 +49,6 @@ static const unsigned maxArrayIndex = 0xFFFFFFFEU;
static const unsigned sparseArrayCutoff = 10000;
static const unsigned minDensityMultiplier = 8;
static const unsigned copyingSortCutoff = 50000;
const ClassInfo ArrayInstance::info = {"Array", 0, 0, 0};
static inline size_t storageSize(unsigned vectorLength)
......@@ -355,7 +354,7 @@ void ArrayInstance::getPropertyNames(ExecState* exec, PropertyNameArray& propert
JSObject::getPropertyNames(exec, propertyNames);
}
void ArrayInstance::increaseVectorLength(unsigned newLength)
bool ArrayInstance::increaseVectorLength(unsigned newLength)
{
ArrayStorage* storage = m_storage;
......@@ -364,12 +363,16 @@ void ArrayInstance::increaseVectorLength(unsigned newLength)
unsigned newVectorLength = increasedVectorLength(newLength);
storage = static_cast<ArrayStorage*>(fastRealloc(storage, storageSize(newVectorLength)));
if (!storage)
return false;
m_vectorLength = newVectorLength;
for (unsigned i = vectorLength; i < newVectorLength; ++i)
storage->m_vector[i] = 0;
m_storage = storage;
return true;
}
void ArrayInstance::setLength(unsigned newLength)
......@@ -434,63 +437,97 @@ static int compareByStringPairForQSort(const void* a, const void* b)
return compare(va->second, vb->second);
}
class ArraySortComparator {
public:
ArraySortComparator(ExecState* exec) : m_exec(exec) {}
bool operator()(JSValue* va, JSValue* vb)
{
ASSERT(!va->isUndefined());
ASSERT(!vb->isUndefined());
return compare(va->toString(m_exec), vb->toString(m_exec)) < 0;
}
private:
ExecState* m_exec;
};
void ArrayInstance::sort(ExecState* exec)
{
unsigned lengthNotIncludingUndefined = compactForSorting();
if (m_storage->m_sparseValueMap) {
exec->setException(Error::create(exec, GeneralError, "Out of memory"));
return;
}
if (lengthNotIncludingUndefined < copyingSortCutoff) {
// Converting JavaScript values to strings can be expensive, so we do it once up front and sort based on that.
// This is a considerable improvement over doing it twice per comparison, though it requires a large temporary
// buffer. For large arrays, we fall back to a qsort on the JavaScriptValues to avoid creating copies.
Vector<std::pair<JSValue*, UString> > values(lengthNotIncludingUndefined);
for (size_t i = 0; i < lengthNotIncludingUndefined; i++) {
JSValue* value = m_storage->m_vector[i];
ASSERT(!value->isUndefined());
values[i].first = value;
values[i].second = value->toString(exec);
}
if (!lengthNotIncludingUndefined)
return;
// Converting JavaScript values to strings can be expensive, so we do it once up front and sort based on that.
// This is a considerable improvement over doing it twice per comparison, though it requires a large temporary
// buffer. Besides, this protects us from crashing if some objects have custom toString methods that return
// random or otherwise changing results, effectively making compare function inconsistent.
Vector<std::pair<JSValue*, UString> > values(lengthNotIncludingUndefined);
if (!values.begin()) {
exec->setException(Error::create(exec, GeneralError, "Out of memory"));
return;
}
// FIXME: Since we sort by string value, a fast algorithm might be to use a radix sort. That would be O(N) rather
// than O(N log N).
for (size_t i = 0; i < lengthNotIncludingUndefined; i++) {
JSValue* value = m_storage->m_vector[i];
ASSERT(!value->isUndefined());
values[i].first = value;
values[i].second = value->toString(exec);
}
if (exec->hadException())
return;
// FIXME: Since we sort by string value, a fast algorithm might be to use a radix sort. That would be O(N) rather
// than O(N log N).
#if HAVE(MERGESORT)
mergesort(values.begin(), values.size(), sizeof(std::pair<JSValue*, UString>), compareByStringPairForQSort);
mergesort(values.begin(), values.size(), sizeof(std::pair<JSValue*, UString>), compareByStringPairForQSort);
#else
qsort(values.begin(), values.size(), sizeof(std::pair<JSValue*, UString>), compareByStringPairForQSort);
// FIXME: QSort may not be stable in some implementations. ECMAScript-262 does not require this, but in practice, browsers perform stable sort.
qsort(values.begin(), values.size(), sizeof(std::pair<JSValue*, UString>), compareByStringPairForQSort);
#endif
for (size_t i = 0; i < lengthNotIncludingUndefined; i++)
m_storage->m_vector[i] = values[i].first;
return;
}
std::sort(m_storage->m_vector, m_storage->m_vector + lengthNotIncludingUndefined, ArraySortComparator(exec));
for (size_t i = 0; i < lengthNotIncludingUndefined; i++)
m_storage->m_vector[i] = values[i].first;
}
struct CompareWithCompareFunctionArguments {
CompareWithCompareFunctionArguments(ExecState* exec, JSObject* cf)
: exec(exec)
, compareFunction(cf)
, globalThisValue(exec->globalThisValue())
struct AVLTreeNodeForArrayCompare {
JSValue* value;
// Child pointers. The high bit of gt is robbed and used as the
// balance factor sign. The high bit of lt is robbed and used as
// the magnitude of the balance factor.
int32_t gt;
int32_t lt;
};
struct AVLTreeAbstractorForArrayCompare {
typedef int32_t handle; // Handle is an index into m_nodes vector.
typedef JSValue* key;
typedef int32_t size;
Vector<AVLTreeNodeForArrayCompare> m_nodes;
ExecState* m_exec;
JSObject* m_compareFunction;
JSObject* m_globalThisValue;
handle get_less(handle h) { return m_nodes[h].lt & 0x7FFFFFFF; }
void set_less(handle h, handle lh) { m_nodes[h].lt &= 0x80000000; m_nodes[h].lt |= lh; }
handle get_greater(handle h) { return m_nodes[h].gt & 0x7FFFFFFF; }
void set_greater(handle h, handle gh) { m_nodes[h].gt &= 0x80000000; m_nodes[h].gt |= gh; }
int get_balance_factor(handle h)
{
if (m_nodes[h].gt & 0x80000000)
return -1;
return static_cast<unsigned>(m_nodes[h].lt) >> 31;
}
void set_balance_factor(handle h, int bf)
{
if (bf == 0) {
m_nodes[h].lt &= 0x7FFFFFFF;
m_nodes[h].gt &= 0x7FFFFFFF;
} else {
m_nodes[h].lt |= 0x80000000;
if (bf < 0)
m_nodes[h].gt |= 0x80000000;
}
}
bool operator()(JSValue* va, JSValue* vb)
int compare_key_key(key va, key vb)
{
ASSERT(!va->isUndefined());
ASSERT(!vb->isUndefined());
......@@ -498,23 +535,102 @@ struct CompareWithCompareFunctionArguments {
List arguments;
arguments.append(va);
arguments.append(vb);
double compareResult = compareFunction->call(exec, globalThisValue, arguments)->toNumber(exec);
return compareResult < 0;
double compareResult = m_compareFunction->call(m_exec, m_globalThisValue, arguments)->toNumber(m_exec);
return (compareResult < 0) ? -1 : 1; // Not passing equality through, because we need to store all values, even if equivalent.
}
ExecState* exec;
JSObject* compareFunction;
JSObject* globalThisValue;
int compare_key_node(key k, handle h) { return compare_key_key(k, m_nodes[h].value); }
int compare_node_node(handle h1, handle h2) { return compare_key_key(m_nodes[h1].value, m_nodes[h2].value); }
static handle null() { return 0x7FFFFFFF; }
};
void ArrayInstance::sort(ExecState* exec, JSObject* compareFunction)
{
size_t lengthNotIncludingUndefined = compactForSorting();
// The maximum tree depth is compiled in - but the caller is clearly up to no good
// if a larger array is passed.
ASSERT(m_length <= static_cast<unsigned>(std::numeric_limits<int>::max()));
if (m_length > static_cast<unsigned>(std::numeric_limits<int>::max()))
return;
if (!m_length)
return;
unsigned usedVectorLength = min(m_length, m_vectorLength);
AVLTree<AVLTreeAbstractorForArrayCompare, 44> tree; // Depth 44 is enough for 2^31 items
tree.abstractor().m_exec = exec;
tree.abstractor().m_compareFunction = compareFunction;
tree.abstractor().m_globalThisValue = exec->globalThisValue();
tree.abstractor().m_nodes.resize(usedVectorLength + (m_storage->m_sparseValueMap ? m_storage->m_sparseValueMap->size() : 0));
if (!tree.abstractor().m_nodes.begin()) {
exec->setException(Error::create(exec, GeneralError, "Out of memory"));
return;
}
unsigned numDefined = 0;
unsigned numUndefined = 0;
// Iterate over the array, ignoring missing values, counting undefined ones, and inserting all other ones into the tree.
for (; numDefined < usedVectorLength; ++numDefined) {
JSValue* v = m_storage->m_vector[numDefined];
if (!v || v->isUndefined())
break;
tree.abstractor().m_nodes[numDefined].value = v;
tree.insert(numDefined);
}
for (unsigned i = numDefined; i < usedVectorLength; ++i) {
if (JSValue* v = m_storage->m_vector[i]) {
if (v->isUndefined())
++numUndefined;
else {
tree.abstractor().m_nodes[numDefined].value = v;
tree.insert(numDefined);
++numDefined;
}
}
}
unsigned newUsedVectorLength = numDefined + numUndefined;
if (SparseArrayValueMap* map = m_storage->m_sparseValueMap) {
newUsedVectorLength += map->size();
if (newUsedVectorLength > m_vectorLength) {
if (!increaseVectorLength(newUsedVectorLength)) {
exec->setException(Error::create(exec, GeneralError, "Out of memory"));
return;
}
}
SparseArrayValueMap::iterator end = map->end();
for (SparseArrayValueMap::iterator it = map->begin(); it != end; ++it) {
tree.abstractor().m_nodes[numDefined].value = it->second;
tree.insert(numDefined);
++numDefined;
}
delete map;
m_storage->m_sparseValueMap = 0;
}
// FIXME: A tree sort using a perfectly balanced tree (e.g. an AVL tree) could do an even
// better job of minimizing compares.
ASSERT(tree.abstractor().m_nodes.size() >= numDefined);
std::sort(m_storage->m_vector, m_storage->m_vector + lengthNotIncludingUndefined, CompareWithCompareFunctionArguments(exec, compareFunction));
// Copy the values back into m_storage.
AVLTree<AVLTreeAbstractorForArrayCompare, 44>::Iterator iter;
iter.start_iter_least(tree);
for (unsigned i = 0; i < numDefined; ++i) {
m_storage->m_vector[i] = tree.abstractor().m_nodes[*iter].value;
++iter;
}
// Put undefined values back in.
for (unsigned i = numDefined; i < newUsedVectorLength; ++i)
m_storage->m_vector[i] = jsUndefined();
// Ensure that unused values in the vector are zeroed out.
for (unsigned i = newUsedVectorLength; i < usedVectorLength; ++i)
m_storage->m_vector[i] = 0;
}
unsigned ArrayInstance::compactForSorting()
......@@ -545,7 +661,8 @@ unsigned ArrayInstance::compactForSorting()
if (SparseArrayValueMap* map = storage->m_sparseValueMap) {
newUsedVectorLength += map->size();
if (newUsedVectorLength > m_vectorLength) {
increaseVectorLength(newUsedVectorLength);
if (!increaseVectorLength(newUsedVectorLength))
return 0;
storage = m_storage;
}
......
......@@ -58,7 +58,7 @@ namespace KJS {
bool inlineGetOwnPropertySlot(ExecState*, unsigned propertyName, PropertySlot&);
void setLength(unsigned);
void increaseVectorLength(unsigned newLength);
bool increaseVectorLength(unsigned newLength);
unsigned compactForSorting();
......
......@@ -147,7 +147,7 @@ function realsort( x, y ) {
return ( x.valueOf() == y.valueOf() ? 0 : ( x.valueOf() > y.valueOf() ? 1 : -1 ) );
}
function comparefn3( x, y ) {
return ( x == y ? 0 : ( x > y ? 1: -1 ) );
return ( +x == +y ? 0 : ( x > y ? 1 : -1 ) );
}
function clone( source, target ) {
for (i = 0; i < source.length; i++ ) {
......
This diff is collapsed.
......@@ -408,7 +408,8 @@ namespace WTF {
: m_size(size)
, m_buffer(size)
{
TypeOperations::initialize(begin(), end());
if (begin())
TypeOperations::initialize(begin(), end());
}
~Vector()
......@@ -489,7 +490,8 @@ namespace WTF {
: m_size(size)
, m_buffer(size)
{
TypeOperations::uninitializedFill(begin(), end(), val);
if (begin())
TypeOperations::uninitializedFill(begin(), end(), val);
}
void fill(const T&, size_t);
......@@ -519,7 +521,8 @@ namespace WTF {
: m_size(other.size())
, m_buffer(other.capacity())
{
TypeOperations::uninitializedCopy(other.begin(), other.end(), begin());
if (begin())
TypeOperations::uninitializedCopy(other.begin(), other.end(), begin());
}
template<typename T, size_t inlineCapacity>
......@@ -528,7 +531,8 @@ namespace WTF {
: m_size(other.size())
, m_buffer(other.capacity())
{
TypeOperations::uninitializedCopy(other.begin(), other.end(), begin());
if (begin())
TypeOperations::uninitializedCopy(other.begin(), other.end(), begin());
}
template<typename T, size_t inlineCapacity>
......@@ -542,6 +546,8 @@ namespace WTF {
else if (other.size() > capacity()) {
clear();
reserveCapacity(other.size());
if (!begin())
return *this;
}
std::copy(other.begin(), other.begin() + size(), begin());
......@@ -563,6 +569,8 @@ namespace WTF {
else if (other.size() > capacity()) {
clear();
reserveCapacity(other.size());
if (!begin())
return *this;
}
std::copy(other.begin(), other.begin() + size(), begin());
......@@ -580,6 +588,8 @@ namespace WTF {
else if (newSize > capacity()) {
clear();
reserveCapacity(newSize);
if (!begin())
return;
}
std::fill(begin(), end(), val);
......
2008-05-21 Alexey Proskuryakov <ap@webkit.org>
Reviewed by Darin.
<rdar://problem/5908520> REGRESSION (3.1.1-r33033): Crash in WebKit when opening or refreshing page on people.com
https://bugs.webkit.org/show_bug.cgi?id=18687
REGRESSION(r32220): ecma/Array/15.4.4.5-3.js test now fails in GMT(BST)
* fast/js/comparefn-sort-stability-expected.txt: Added.
* fast/js/comparefn-sort-stability.html: Added.
* fast/js/resources/comparefn-sort-stability.js: Added.
* fast/js/resources/sort-randomly.js: Added.
* fast/js/resources/sort-stability.js: Added.
* fast/js/sort-randomly-expected.txt: Added.
* fast/js/sort-randomly.html: Added.
* fast/js/sort-stability-expected.txt: Added.
* fast/js/sort-stability.html: Added.
2008-05-21 Alexey Proskuryakov <ap@webkit.org>
Reviewed by Darin.
This tests that sort(compareFn) is a stable sort.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS arr[0] is sortArr[0]
PASS arr[1] is sortArr[2]
PASS arr[2] is sortArr[1]
PASS arr[3] is sortArr[3]
PASS arr[0] is sortArr[0]
PASS arr[1] is sortArr[2]
PASS arr[2] is sortArr[1]
PASS arr[3] is sortArr[3]
PASS successfullyParsed is true
TEST COMPLETE
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<link rel="stylesheet" href="resources/js-test-style.css">
<script src="resources/js-test-pre.js"></script>
</head>
<body>
<p id="description"></p>
<div id="console"></div>
<script src="resources/comparefn-sort-stability.js"></script>
<script src="resources/js-test-post.js"></script>
</body>
</html>
description(
"This tests that sort(compareFn) is a stable sort."
);
function clone(source, target) {
for (i = 0; i < source.length; i++) {
target[i] = source[i];
}
}
var arr = [];
arr[0] = new Number(1);
arr[1] = new Number(2);
arr[2] = new Number(1);
arr[3] = new Number(2);
var sortArr = [];
clone(arr, sortArr);
sortArr.sort(function(a,b) { return a - b; });
shouldBe('arr[0]', 'sortArr[0]');
shouldBe('arr[1]', 'sortArr[2]');
shouldBe('arr[2]', 'sortArr[1]');
shouldBe('arr[3]', 'sortArr[3]');
// Just try again...
sortArr.sort(function(a,b) { return a - b; });
shouldBe('arr[0]', 'sortArr[0]');
shouldBe('arr[1]', 'sortArr[2]');
shouldBe('arr[2]', 'sortArr[1]');
shouldBe('arr[3]', 'sortArr[3]');
var successfullyParsed = true;
description(
"This tests that passing an inconsistent compareFn to sort() doesn't cause a crash."
);
for (var attempt = 0; attempt < 100; ++attempt) {
var arr = [];
for (var i = 0; i < 64; ++i)
arr[i] = i;
arr.sort(function() { return 0.5 - Math.random(); });
}
// Sorting objects that change each time sort() looks at them is the same as using a random compareFn.
function RandomObject() {
this.toString = function() { return (Math.random() * 100).toString(); }
}
for (var attempt = 0; attempt < 100; ++attempt) {
var arr = [];
for (var i = 0; i < 64; ++i)
arr[i] = new RandomObject;
arr.sort();
}
var successfullyParsed = true;
description(
"This tests that sort() is a stable sort."
);
function clone(source, target) {
for (i = 0; i < source.length; i++) {
target[i] = source[i];
}
}
var arr = [];
arr[0] = new Number(1);
arr[1] = new Number(2);
arr[2] = new Number(1);
arr[3] = new Number(2);