Changeset 33967 in webkit


Ignore:
Timestamp:
May 21, 2008 10:17:37 AM (16 years ago)
Author:
ap@webkit.org
Message:

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).
Location:
trunk
Files:
10 added
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/JavaScriptCore/ChangeLog

    r33965 r33967  
     12008-05-21  Alexey Proskuryakov  <ap@webkit.org>
     2
     3        Reviewed by Darin.
     4
     5        <rdar://problem/5908520> REGRESSION (3.1.1-r33033): Crash in WebKit when opening or
     6        refreshing page on people.com
     7
     8        The problem was that STL algorithms do not work with non-conformant comparators, and the
     9        site used sort(function() { return 0.5 - Math.random(); } to randomly shuffle an array.
     10
     11        https://bugs.webkit.org/show_bug.cgi?id=18687
     12        REGRESSION(r32220): ecma/Array/15.4.4.5-3.js test now fails in GMT(BST)
     13
     14        Besides relying on sort stability, this test was just broken, and kept failing with the
     15        new stable sort.
     16
     17        Tests: fast/js/sort-randomly.html
     18               fast/js/sort-stability.html
     19               fast/js/comparefn-sort-stability.html
     20
     21        * kjs/avl_tree.h: Added an AVL tree implementation.
     22
     23        * JavaScriptCore.xcodeproj/project.pbxproj:
     24        * wtf/AVLTree.h: Added.
     25        Added an AVL tree implementation.
     26
     27        * kjs/array_instance.cpp:
     28        (KJS::ArrayInstance::increaseVectorLength):
     29        (KJS::ArrayInstance::sort):
     30        (KJS::AVLTreeAbstractorForArrayCompare::get_less):
     31        (KJS::AVLTreeAbstractorForArrayCompare::set_less):
     32        (KJS::AVLTreeAbstractorForArrayCompare::get_greater):
     33        (KJS::AVLTreeAbstractorForArrayCompare::set_greater):
     34        (KJS::AVLTreeAbstractorForArrayCompare::get_balance_factor):
     35        (KJS::AVLTreeAbstractorForArrayCompare::set_balance_factor):
     36        (KJS::AVLTreeAbstractorForArrayCompare::compare_key_key):
     37        (KJS::AVLTreeAbstractorForArrayCompare::compare_key_node):
     38        (KJS::AVLTreeAbstractorForArrayCompare::compare_node_node):
     39        (KJS::AVLTreeAbstractorForArrayCompare::null):
     40        (KJS::ArrayInstance::compactForSorting):
     41       
     42        * kjs/array_instance.h: increaseVectorLength() now returns a bool to indicate whether it was
     43        successful.
     44
     45        * wtf/Vector.h:
     46        (WTF::Vector::Vector):
     47        (WTF::::operator=):
     48        (WTF::::fill):
     49        Make these methods fail instead of crash when allocation fails, matching resize() and
     50        reserveCapacity(), which already had this behavior. Callers need to check for null buffer
     51        after making any Vector call that can try to allocate.
     52
     53        * tests/mozilla/ecma/Array/15.4.4.5-3.js: Fixed the test to use a consistent sort function,
     54        as suggested in comments to a Mozilla bug filed about it (I'll keep tracking the bug to see
     55        what the final resolution is).
     56
    1572008-05-20  Kevin McCullough  <kmccullough@apple.com>
    258
  • trunk/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj

    r33466 r33967  
    204204                E195679609E7CF1200B89D13 /* UnicodeIcu.h in Headers */ = {isa = PBXBuildFile; fileRef = E195678F09E7CF1200B89D13 /* UnicodeIcu.h */; settings = {ATTRIBUTES = (Private, ); }; };
    205205                E195679809E7CF1200B89D13 /* Unicode.h in Headers */ = {isa = PBXBuildFile; fileRef = E195679409E7CF1200B89D13 /* Unicode.h */; settings = {ATTRIBUTES = (Private, ); }; };
     206                E1A596380DE3E1C300C17E37 /* AVLTree.h in Headers */ = {isa = PBXBuildFile; fileRef = E1A596370DE3E1C300C17E37 /* AVLTree.h */; };
    206207                E1A862A90D7EBB76001EC6AA /* CollatorICU.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E1A862A80D7EBB76001EC6AA /* CollatorICU.cpp */; settings = {COMPILER_FLAGS = "-fno-strict-aliasing"; }; };
    207208                E1A862AB0D7EBB7D001EC6AA /* Collator.h in Headers */ = {isa = PBXBuildFile; fileRef = E1A862AA0D7EBB7D001EC6AA /* Collator.h */; settings = {ATTRIBUTES = (Private, ); }; };
     
    517518                E195678F09E7CF1200B89D13 /* UnicodeIcu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UnicodeIcu.h; sourceTree = "<group>"; };
    518519                E195679409E7CF1200B89D13 /* Unicode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Unicode.h; sourceTree = "<group>"; };
     520                E1A596370DE3E1C300C17E37 /* AVLTree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AVLTree.h; sourceTree = "<group>"; };
    519521                E1A862A80D7EBB76001EC6AA /* CollatorICU.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CollatorICU.cpp; sourceTree = "<group>"; };
    520522                E1A862AA0D7EBB7D001EC6AA /* Collator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Collator.h; sourceTree = "<group>"; };
     
    786788                                65E217B808E7EECC0023E5F6 /* Assertions.cpp */,
    787789                                65E217B708E7EECC0023E5F6 /* Assertions.h */,
     790                                E1A596370DE3E1C300C17E37 /* AVLTree.h */,
    788791                                5186111D0CC824830081412B /* Deque.h */,
    789792                                938C4F6B0CA06BCE00D9310A /* DisallowCType.h */,
     
    11561159                                95742F660DD11F5A000917FB /* Profile.h in Headers */,
    11571160                                5DE3D0F50DD8DDFB00468714 /* WebKitAvailability.h in Headers */,
     1161                                E1A596380DE3E1C300C17E37 /* AVLTree.h in Headers */,
    11581162                        );
    11591163                        runOnlyForDeploymentPostprocessing = 0;
  • trunk/JavaScriptCore/kjs/array_instance.cpp

    r33038 r33967  
    2626#include "PropertyNameArray.h"
    2727#include <wtf/Assertions.h>
     28#include <wtf/AVLTree.h>
    2829
    2930using std::min;
     
    4849static const unsigned sparseArrayCutoff = 10000;
    4950static const unsigned minDensityMultiplier = 8;
    50 
    51 static const unsigned copyingSortCutoff = 50000;
    5251
    5352const ClassInfo ArrayInstance::info = {"Array", 0, 0, 0};
     
    356355}
    357356
    358 void ArrayInstance::increaseVectorLength(unsigned newLength)
     357bool ArrayInstance::increaseVectorLength(unsigned newLength)
    359358{
    360359    ArrayStorage* storage = m_storage;
     
    365364
    366365    storage = static_cast<ArrayStorage*>(fastRealloc(storage, storageSize(newVectorLength)));
     366    if (!storage)
     367        return false;
     368
    367369    m_vectorLength = newVectorLength;
    368370
     
    371373
    372374    m_storage = storage;
     375    return true;
    373376}
    374377
     
    435438}
    436439
    437 class ArraySortComparator {
    438 public:
    439     ArraySortComparator(ExecState* exec) : m_exec(exec) {}
    440 
    441     bool operator()(JSValue* va, JSValue* vb)
     440void ArrayInstance::sort(ExecState* exec)
     441{
     442    unsigned lengthNotIncludingUndefined = compactForSorting();
     443    if (m_storage->m_sparseValueMap) {
     444        exec->setException(Error::create(exec, GeneralError, "Out of memory"));
     445        return;
     446    }
     447
     448    if (!lengthNotIncludingUndefined)
     449        return;
     450
     451    // Converting JavaScript values to strings can be expensive, so we do it once up front and sort based on that.
     452    // This is a considerable improvement over doing it twice per comparison, though it requires a large temporary
     453    // buffer. Besides, this protects us from crashing if some objects have custom toString methods that return
     454    // random or otherwise changing results, effectively making compare function inconsistent.
     455
     456    Vector<std::pair<JSValue*, UString> > values(lengthNotIncludingUndefined);
     457    if (!values.begin()) {
     458        exec->setException(Error::create(exec, GeneralError, "Out of memory"));
     459        return;
     460    }
     461
     462    for (size_t i = 0; i < lengthNotIncludingUndefined; i++) {
     463        JSValue* value = m_storage->m_vector[i];
     464        ASSERT(!value->isUndefined());
     465        values[i].first = value;
     466        values[i].second = value->toString(exec);
     467    }
     468
     469    if (exec->hadException())
     470        return;
     471
     472    // FIXME: Since we sort by string value, a fast algorithm might be to use a radix sort. That would be O(N) rather
     473    // than O(N log N).
     474
     475#if HAVE(MERGESORT)
     476    mergesort(values.begin(), values.size(), sizeof(std::pair<JSValue*, UString>), compareByStringPairForQSort);
     477#else
     478    // FIXME: QSort may not be stable in some implementations. ECMAScript-262 does not require this, but in practice, browsers perform stable sort.
     479    qsort(values.begin(), values.size(), sizeof(std::pair<JSValue*, UString>), compareByStringPairForQSort);
     480#endif
     481
     482    for (size_t i = 0; i < lengthNotIncludingUndefined; i++)
     483        m_storage->m_vector[i] = values[i].first;
     484}
     485
     486struct AVLTreeNodeForArrayCompare {
     487    JSValue* value;
     488
     489    // Child pointers.  The high bit of gt is robbed and used as the
     490    // balance factor sign.  The high bit of lt is robbed and used as
     491    // the magnitude of the balance factor.
     492    int32_t gt;
     493    int32_t lt;
     494};
     495
     496struct AVLTreeAbstractorForArrayCompare {
     497    typedef int32_t handle; // Handle is an index into m_nodes vector.
     498    typedef JSValue* key;
     499    typedef int32_t size;
     500
     501    Vector<AVLTreeNodeForArrayCompare> m_nodes;
     502    ExecState* m_exec;
     503    JSObject* m_compareFunction;
     504    JSObject* m_globalThisValue;
     505
     506    handle get_less(handle h) { return m_nodes[h].lt & 0x7FFFFFFF; }
     507    void set_less(handle h, handle lh) { m_nodes[h].lt &= 0x80000000; m_nodes[h].lt |= lh; }
     508    handle get_greater(handle h) { return m_nodes[h].gt & 0x7FFFFFFF; }
     509    void set_greater(handle h, handle gh) { m_nodes[h].gt &= 0x80000000; m_nodes[h].gt |= gh; }
     510
     511    int get_balance_factor(handle h)
     512    {
     513        if (m_nodes[h].gt & 0x80000000)
     514            return -1;
     515        return static_cast<unsigned>(m_nodes[h].lt) >> 31;
     516    }
     517
     518    void set_balance_factor(handle h, int bf)
     519    {
     520        if (bf == 0) {
     521            m_nodes[h].lt &= 0x7FFFFFFF;
     522            m_nodes[h].gt &= 0x7FFFFFFF;
     523        } else {
     524            m_nodes[h].lt |= 0x80000000;
     525            if (bf < 0)
     526                m_nodes[h].gt |= 0x80000000;
     527        }
     528    }
     529
     530    int compare_key_key(key va, key vb)
    442531    {
    443532        ASSERT(!va->isUndefined());
    444533        ASSERT(!vb->isUndefined());
    445         return compare(va->toString(m_exec), vb->toString(m_exec)) < 0;
    446     }
    447 
    448 private:
    449     ExecState* m_exec;
    450 };
    451 
    452 void ArrayInstance::sort(ExecState* exec)
    453 {
    454     unsigned lengthNotIncludingUndefined = compactForSorting();
    455 
    456     if (lengthNotIncludingUndefined < copyingSortCutoff) {
    457         // Converting JavaScript values to strings can be expensive, so we do it once up front and sort based on that.
    458         // This is a considerable improvement over doing it twice per comparison, though it requires a large temporary
    459         // buffer.  For large arrays, we fall back to a qsort on the JavaScriptValues to avoid creating copies.
    460 
    461         Vector<std::pair<JSValue*, UString> > values(lengthNotIncludingUndefined);
    462         for (size_t i = 0; i < lengthNotIncludingUndefined; i++) {
    463             JSValue* value = m_storage->m_vector[i];
    464             ASSERT(!value->isUndefined());
    465             values[i].first = value;
    466             values[i].second = value->toString(exec);
    467         }
    468 
    469         // FIXME: Since we sort by string value, a fast algorithm might be to use a radix sort. That would be O(N) rather
    470         // than O(N log N).
    471 
    472 #if HAVE(MERGESORT)
    473         mergesort(values.begin(), values.size(), sizeof(std::pair<JSValue*, UString>), compareByStringPairForQSort);
    474 #else
    475         qsort(values.begin(), values.size(), sizeof(std::pair<JSValue*, UString>), compareByStringPairForQSort);
    476 #endif
    477         for (size_t i = 0; i < lengthNotIncludingUndefined; i++)
    478             m_storage->m_vector[i] = values[i].first;
    479         return;
    480     }
    481 
    482     std::sort(m_storage->m_vector, m_storage->m_vector + lengthNotIncludingUndefined, ArraySortComparator(exec));
    483 }
    484 
    485 struct CompareWithCompareFunctionArguments {
    486     CompareWithCompareFunctionArguments(ExecState* exec, JSObject* cf)
    487         : exec(exec)
    488         , compareFunction(cf)
    489         , globalThisValue(exec->globalThisValue())
    490     {
    491     }
    492 
    493     bool operator()(JSValue* va, JSValue* vb)
    494     {
    495         ASSERT(!va->isUndefined());
    496         ASSERT(!vb->isUndefined());
    497534
    498535        List arguments;
    499536        arguments.append(va);
    500537        arguments.append(vb);
    501         double compareResult = compareFunction->call(exec, globalThisValue, arguments)->toNumber(exec);
    502         return compareResult < 0;
    503     }
    504 
    505     ExecState* exec;
    506     JSObject* compareFunction;
    507     JSObject* globalThisValue;
     538        double compareResult = m_compareFunction->call(m_exec, m_globalThisValue, arguments)->toNumber(m_exec);
     539        return (compareResult < 0) ? -1 : 1; // Not passing equality through, because we need to store all values, even if equivalent.
     540    }
     541
     542    int compare_key_node(key k, handle h) { return compare_key_key(k, m_nodes[h].value); }
     543    int compare_node_node(handle h1, handle h2) { return compare_key_key(m_nodes[h1].value, m_nodes[h2].value); }
     544
     545    static handle null() { return 0x7FFFFFFF; }
    508546};
    509547
    510548void ArrayInstance::sort(ExecState* exec, JSObject* compareFunction)
    511549{
    512     size_t lengthNotIncludingUndefined = compactForSorting();
    513 
    514     // FIXME: A tree sort using a perfectly balanced tree (e.g. an AVL tree) could do an even
    515     // better job of minimizing compares.
    516 
    517     std::sort(m_storage->m_vector, m_storage->m_vector + lengthNotIncludingUndefined, CompareWithCompareFunctionArguments(exec, compareFunction));
     550    // The maximum tree depth is compiled in - but the caller is clearly up to no good
     551    // if a larger array is passed.
     552    ASSERT(m_length <= static_cast<unsigned>(std::numeric_limits<int>::max()));
     553    if (m_length > static_cast<unsigned>(std::numeric_limits<int>::max()))
     554        return;
     555
     556    if (!m_length)
     557        return;
     558
     559    unsigned usedVectorLength = min(m_length, m_vectorLength);
     560
     561    AVLTree<AVLTreeAbstractorForArrayCompare, 44> tree; // Depth 44 is enough for 2^31 items
     562    tree.abstractor().m_exec = exec;
     563    tree.abstractor().m_compareFunction = compareFunction;
     564    tree.abstractor().m_globalThisValue = exec->globalThisValue();
     565    tree.abstractor().m_nodes.resize(usedVectorLength + (m_storage->m_sparseValueMap ? m_storage->m_sparseValueMap->size() : 0));
     566
     567    if (!tree.abstractor().m_nodes.begin()) {
     568        exec->setException(Error::create(exec, GeneralError, "Out of memory"));
     569        return;
     570    }
     571
     572    unsigned numDefined = 0;
     573    unsigned numUndefined = 0;
     574
     575    // Iterate over the array, ignoring missing values, counting undefined ones, and inserting all other ones into the tree.
     576    for (; numDefined < usedVectorLength; ++numDefined) {
     577        JSValue* v = m_storage->m_vector[numDefined];
     578        if (!v || v->isUndefined())
     579            break;
     580        tree.abstractor().m_nodes[numDefined].value = v;
     581        tree.insert(numDefined);
     582    }
     583    for (unsigned i = numDefined; i < usedVectorLength; ++i) {
     584        if (JSValue* v = m_storage->m_vector[i]) {
     585            if (v->isUndefined())
     586                ++numUndefined;
     587            else {
     588                tree.abstractor().m_nodes[numDefined].value = v;
     589                tree.insert(numDefined);
     590                ++numDefined;
     591            }
     592        }
     593    }
     594
     595    unsigned newUsedVectorLength = numDefined + numUndefined;
     596
     597    if (SparseArrayValueMap* map = m_storage->m_sparseValueMap) {
     598        newUsedVectorLength += map->size();
     599        if (newUsedVectorLength > m_vectorLength) {
     600            if (!increaseVectorLength(newUsedVectorLength)) {
     601                exec->setException(Error::create(exec, GeneralError, "Out of memory"));
     602                return;
     603            }
     604        }
     605
     606        SparseArrayValueMap::iterator end = map->end();
     607        for (SparseArrayValueMap::iterator it = map->begin(); it != end; ++it) {
     608            tree.abstractor().m_nodes[numDefined].value = it->second;
     609            tree.insert(numDefined);
     610            ++numDefined;
     611        }
     612
     613        delete map;
     614        m_storage->m_sparseValueMap = 0;
     615    }
     616
     617    ASSERT(tree.abstractor().m_nodes.size() >= numDefined);
     618
     619    // Copy the values back into m_storage.
     620    AVLTree<AVLTreeAbstractorForArrayCompare, 44>::Iterator iter;
     621    iter.start_iter_least(tree);
     622    for (unsigned i = 0; i < numDefined; ++i) {
     623        m_storage->m_vector[i] = tree.abstractor().m_nodes[*iter].value;
     624        ++iter;
     625    }
     626
     627    // Put undefined values back in.
     628    for (unsigned i = numDefined; i < newUsedVectorLength; ++i)
     629        m_storage->m_vector[i] = jsUndefined();
     630
     631    // Ensure that unused values in the vector are zeroed out.
     632    for (unsigned i = newUsedVectorLength; i < usedVectorLength; ++i)
     633        m_storage->m_vector[i] = 0;
    518634}
    519635
     
    546662        newUsedVectorLength += map->size();
    547663        if (newUsedVectorLength > m_vectorLength) {
    548             increaseVectorLength(newUsedVectorLength);
     664            if (!increaseVectorLength(newUsedVectorLength))
     665                return 0;
    549666            storage = m_storage;
    550667        }
  • trunk/JavaScriptCore/kjs/array_instance.h

    r30534 r33967  
    5959
    6060    void setLength(unsigned);
    61     void increaseVectorLength(unsigned newLength);
     61    bool increaseVectorLength(unsigned newLength);
    6262   
    6363    unsigned compactForSorting();   
  • trunk/JavaScriptCore/tests/mozilla/ecma/Array/15.4.4.5-3.js

    r11995 r33967  
    148148}
    149149function comparefn3( x, y ) {
    150     return ( x == y ? 0 : ( x > y ? 1: -1 ) );
     150  return ( +x == +y ? 0 : ( x > y ? 1 : -1 ) );
    151151}
    152152function clone( source, target ) {
  • trunk/JavaScriptCore/wtf/Vector.h

    r31807 r33967  
    409409            , m_buffer(size)
    410410        {
    411             TypeOperations::initialize(begin(), end());
     411            if (begin())
     412                TypeOperations::initialize(begin(), end());
    412413        }
    413414
     
    490491            , m_buffer(size)
    491492        {
    492             TypeOperations::uninitializedFill(begin(), end(), val);
     493            if (begin())
     494                TypeOperations::uninitializedFill(begin(), end(), val);
    493495        }
    494496
     
    520522        , m_buffer(other.capacity())
    521523    {
    522         TypeOperations::uninitializedCopy(other.begin(), other.end(), begin());
     524        if (begin())
     525            TypeOperations::uninitializedCopy(other.begin(), other.end(), begin());
    523526    }
    524527
     
    529532        , m_buffer(other.capacity())
    530533    {
    531         TypeOperations::uninitializedCopy(other.begin(), other.end(), begin());
     534        if (begin())
     535            TypeOperations::uninitializedCopy(other.begin(), other.end(), begin());
    532536    }
    533537
     
    543547            clear();
    544548            reserveCapacity(other.size());
     549            if (!begin())
     550                return *this;
    545551        }
    546552       
     
    564570            clear();
    565571            reserveCapacity(other.size());
     572            if (!begin())
     573                return *this;
    566574        }
    567575       
     
    581589            clear();
    582590            reserveCapacity(newSize);
     591            if (!begin())
     592                return;
    583593        }
    584594       
  • trunk/LayoutTests/ChangeLog

    r33966 r33967  
     12008-05-21  Alexey Proskuryakov  <ap@webkit.org>
     2
     3        Reviewed by Darin.
     4
     5        <rdar://problem/5908520> REGRESSION (3.1.1-r33033): Crash in WebKit when opening or refreshing page on people.com
     6
     7        https://bugs.webkit.org/show_bug.cgi?id=18687
     8        REGRESSION(r32220): ecma/Array/15.4.4.5-3.js test now fails in GMT(BST)
     9
     10        * fast/js/comparefn-sort-stability-expected.txt: Added.
     11        * fast/js/comparefn-sort-stability.html: Added.
     12        * fast/js/resources/comparefn-sort-stability.js: Added.
     13        * fast/js/resources/sort-randomly.js: Added.
     14        * fast/js/resources/sort-stability.js: Added.
     15        * fast/js/sort-randomly-expected.txt: Added.
     16        * fast/js/sort-randomly.html: Added.
     17        * fast/js/sort-stability-expected.txt: Added.
     18        * fast/js/sort-stability.html: Added.
     19
    1202008-05-21  Alexey Proskuryakov  <ap@webkit.org>
    221
Note: See TracChangeset for help on using the changeset viewer.