⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 203147 in webkit


Ignore:
Timestamp:
Jul 12, 2016, 6:25:25 PM (10 years ago)
Author:
benjamin@webkit.org
Message:

[JSC] Array.prototype.join() fails some conformance tests
https://bugs.webkit.org/show_bug.cgi?id=159657

Patch by Benjamin Poulain <bpoulain@apple.com> on 2016-07-12
Reviewed by Saam Barati.

Source/JavaScriptCore:

There were a couple of failures:
-separator.toString() was called *before* we get the length

and process ToLength() on it.

-We were using toUInt32() on length instead of ToLength(),

failing on big integers and various negative numbers.

Additionally, I replaced the "fast" ArrayStorage path
by a fully generic implementation that does not depends on StringJoiner.

The reason is StringJoiner was doing poorly on sparse objects
in certain cases.
If you have a sparse object with a length > INT_MAX but very few
indices defined, and you join on the empty string, it should be possible
to join the array (albeit very slowly). With StringJoiner, we fail
because we try to allocate > INT_MAX empty strings in a contiguous vector.

  • runtime/ArrayPrototype.cpp:

(JSC::slowJoin):
(JSC::canUseFastJoin):
(JSC::fastJoin):
(JSC::arrayProtoFuncJoin):
(JSC::join): Deleted.

  • runtime/JSArray.h:

(JSC::toLength):

Source/WTF:

  • wtf/text/AtomicString.cpp:

(WTF::AtomicString::number):

  • wtf/text/AtomicString.h:

LayoutTests:

I removed 3 sputnik tests that are incorrect in the latest spec.
In ES5, Array.prototype.join() was using ToUint32 on the argument:

https://es5.github.io/#x15.4.4.5

In ES6, the function uses ToLength:

https://tc39.github.io/ecma262/#sec-array.prototype.join

The test use Infinity and very large integer as the length.
They are guaranteed to time out or run out of memory.
Even if we waited the hours it takes to run this, the results would be different
from what the tests expect.

  • js/array-join-expected.txt: Added.
  • js/array-join.html: Added.
  • js/script-tests/array-join.js: Added.
Location:
trunk
Files:
3 added
6 deleted
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r203143 r203147  
     12016-07-12  Benjamin Poulain  <bpoulain@apple.com>
     2
     3        [JSC] Array.prototype.join() fails some conformance tests
     4        https://bugs.webkit.org/show_bug.cgi?id=159657
     5
     6        Reviewed by Saam Barati.
     7
     8        I removed 3 sputnik tests that are incorrect in the latest spec.
     9        In ES5, Array.prototype.join() was using ToUint32 on the argument:
     10            https://es5.github.io/#x15.4.4.5
     11        In ES6, the function uses ToLength:
     12            https://tc39.github.io/ecma262/#sec-array.prototype.join
     13
     14        The test use Infinity and very large integer as the length.
     15        They are guaranteed to time out or run out of memory.
     16        Even if we waited the hours it takes to run this, the results would be different
     17        from what the tests expect.
     18
     19        * js/array-join-expected.txt: Added.
     20        * js/array-join.html: Added.
     21        * js/script-tests/array-join.js: Added.
     22
    1232016-07-12  Commit Queue  <commit-queue@webkit.org>
    224
  • trunk/LayoutTests/fast/history/replacestate-nocrash.html

    r140748 r203147  
    77
    88Object.prototype.__defineSetter__("foo",function(){history.replaceState("")});
    9 history.replaceState({foo:1,zzz:Array(1<<22).join("a")});
     9history.replaceState({foo:1,zzz:"a".repeat(1<<22)});
    1010history.state.length;
    1111</script>
  • trunk/LayoutTests/sputnik/Conformance/15_Native_Objects/15.4_Array/15.4.4/15.4.4.5_Array_prototype_join/S15.4.4.5_A4_T3.html

    r203143 r203147  
    8181
    8282//CHECK#1
    83 if (obj.join("") !== "xy") {
    84   testFailed('#1: var obj = {}; obj.join = Array.prototype.join; obj[0] = "x"; obj[1] = "y"; obj[2] = "z"; obj.length = -4294967294; obj.join("") === "xy". Actual: ' + (obj.join("")));
     83if (obj.join("") !== "") {
     84  testFailed('#1: var obj = {}; obj.join = Array.prototype.join; obj[0] = "x"; obj[1] = "y"; obj[2] = "z"; obj.length = -4294967294; obj.join("") === "". Actual: ' + (obj.join("")));
    8585}
    8686
  • trunk/Source/JavaScriptCore/ChangeLog

    r203144 r203147  
     12016-07-12  Benjamin Poulain  <bpoulain@apple.com>
     2
     3        [JSC] Array.prototype.join() fails some conformance tests
     4        https://bugs.webkit.org/show_bug.cgi?id=159657
     5
     6        Reviewed by Saam Barati.
     7
     8        There were a couple of failures:
     9        -separator.toString() was called *before* we get the length
     10         and process ToLength() on it.
     11        -We were using toUInt32() on length instead of ToLength(),
     12         failing on big integers and various negative numbers.
     13
     14        Additionally, I replaced the "fast" ArrayStorage path
     15        by a fully generic implementation that does not depends on StringJoiner.
     16
     17        The reason is StringJoiner was doing poorly on sparse objects
     18        in certain cases.
     19        If you have a sparse object with a length > INT_MAX but very few
     20        indices defined, and you join on the empty string, it should be possible
     21        to join the array (albeit very slowly). With StringJoiner, we fail
     22        because we try to allocate > INT_MAX empty strings in a contiguous vector.
     23
     24        * runtime/ArrayPrototype.cpp:
     25        (JSC::slowJoin):
     26        (JSC::canUseFastJoin):
     27        (JSC::fastJoin):
     28        (JSC::arrayProtoFuncJoin):
     29        (JSC::join): Deleted.
     30        * runtime/JSArray.h:
     31        (JSC::toLength):
     32
    1332016-07-12  Mark Lam  <mark.lam@apple.com>
    234
  • trunk/Source/JavaScriptCore/runtime/ArrayPrototype.cpp

    r203143 r203147  
    487487}
    488488
    489 static inline JSValue join(ExecState& state, JSObject* thisObject, StringView separator)
    490 {
    491     unsigned length = getLength(&state, thisObject);
    492     if (state.hadException())
    493         return jsUndefined();
    494 
     489static JSValue slowJoin(ExecState& exec, JSObject* thisObject, JSString* separator, uint64_t length)
     490{
     491    // 5. If len is zero, return the empty String.
     492    if (!length)
     493        return jsEmptyString(&exec);
     494
     495    VM& vm = exec.vm();
     496
     497    // 6. Let element0 be Get(O, "0").
     498    JSValue element0 = thisObject->getIndex(&exec, 0);
     499    if (vm.exception())
     500        return JSValue();
     501
     502    // 7. If element0 is undefined or null, let R be the empty String; otherwise, let R be ? ToString(element0).
     503    JSString* r = nullptr;
     504    if (element0.isUndefinedOrNull())
     505        r = jsEmptyString(&exec);
     506    else
     507        r = element0.toString(&exec);
     508    if (vm.exception())
     509        return JSValue();
     510
     511    // 8. Let k be 1.
     512    // 9. Repeat, while k < len
     513    // 9.e Increase k by 1..
     514    for (uint64_t k = 1; k < length; ++k) {
     515        // b. Let element be ? Get(O, ! ToString(k)).
     516        JSValue element = thisObject->get(&exec, Identifier::fromString(&exec, AtomicString::number(k)));
     517        if (vm.exception())
     518            return JSValue();
     519
     520        // c. If element is undefined or null, let next be the empty String; otherwise, let next be ? ToString(element).
     521        JSString* next = nullptr;
     522        if (element.isUndefinedOrNull()) {
     523            if (!separator->length())
     524                continue;
     525            next = jsEmptyString(&exec);
     526        } else
     527            next = element.toString(&exec);
     528        if (vm.exception())
     529            return JSValue();
     530
     531        // a. Let S be the String value produced by concatenating R and sep.
     532        // d. Let R be a String value produced by concatenating S and next.
     533        r = JSRopeString::create(vm, r, separator, next);
     534    }
     535    // 10. Return R.
     536    return r;
     537}
     538
     539static inline bool canUseFastJoin(const JSObject* thisObject)
     540{
     541    switch (thisObject->indexingType()) {
     542    case ALL_CONTIGUOUS_INDEXING_TYPES:
     543    case ALL_INT32_INDEXING_TYPES:
     544    case ALL_DOUBLE_INDEXING_TYPES:
     545        return true;
     546    default:
     547        break;
     548    }
     549    return false;
     550}
     551
     552static inline JSValue fastJoin(ExecState& state, JSObject* thisObject, StringView separator, unsigned length)
     553{
    495554    switch (thisObject->indexingType()) {
    496555    case ALL_CONTIGUOUS_INDEXING_TYPES:
     
    543602        return joiner.join(state);
    544603    }
    545     case ALL_ARRAY_STORAGE_INDEXING_TYPES: {
    546         auto& storage = *thisObject->butterfly()->arrayStorage();
    547         if (length > storage.vectorLength())
    548             break;
    549         if (storage.hasHoles() && thisObject->structure(state.vm())->holesMustForwardToPrototype(state.vm()))
    550             break;
    551         JSStringJoiner joiner(state, separator, length);
    552         if (state.hadException())
    553             return jsUndefined();
    554         auto data = storage.vector().data();
    555         for (unsigned i = 0; i < length; ++i) {
    556             if (JSValue value = data[i].get()) {
    557                 if (!joiner.appendWithoutSideEffects(state, value))
    558                     goto generalCase;
    559             } else
    560                 joiner.appendEmptyString();
    561         }
    562         return joiner.join(state);
    563     }
    564604    }
    565605
     
    581621EncodedJSValue JSC_HOST_CALL arrayProtoFuncJoin(ExecState* exec)
    582622{
     623    // 1. Let O be ? ToObject(this value).
    583624    JSObject* thisObject = exec->thisValue().toThis(exec, StrictMode).toObject(exec);
    584625    if (!thisObject)
     
    589630        return JSValue::encode(earlyReturnValue);
    590631
     632    // 2. Let len be ? ToLength(? Get(O, "length")).
     633    double length = toLength(exec, thisObject);
     634    if (exec->hadException())
     635        return JSValue::encode(JSValue());
     636
     637    // 3. If separator is undefined, let separator be the single-element String ",".
    591638    JSValue separatorValue = exec->argument(0);
    592639    if (separatorValue.isUndefined()) {
    593640        const LChar comma = ',';
    594         return JSValue::encode(join(*exec, thisObject, { &comma, 1 }));
    595     }
    596 
    597     JSString* separator = separatorValue.toString(exec);
     641
     642        if (UNLIKELY(length > std::numeric_limits<unsigned>::max() || !canUseFastJoin(thisObject))) {
     643            uint64_t length64 = static_cast<uint64_t>(length);
     644            ASSERT(static_cast<double>(length64) == length);
     645            JSString* jsSeparator = jsSingleCharacterString(exec, comma);
     646            if (exec->hadException())
     647                return JSValue::encode(JSValue());
     648
     649            return JSValue::encode(slowJoin(*exec, thisObject, jsSeparator, length64));
     650        }
     651
     652        unsigned unsignedLength = static_cast<unsigned>(length);
     653        ASSERT(static_cast<double>(unsignedLength) == length);
     654        return JSValue::encode(fastJoin(*exec, thisObject, { &comma, 1 }, unsignedLength));
     655    }
     656
     657    // 4. Let sep be ? ToString(separator).
     658    JSString* jsSeparator = separatorValue.toString(exec);
    598659    if (exec->hadException())
    599660        return JSValue::encode(jsUndefined());
    600     return JSValue::encode(join(*exec, thisObject, separator->view(exec).get()));
     661
     662    if (UNLIKELY(length > std::numeric_limits<unsigned>::max() || !canUseFastJoin(thisObject))) {
     663        uint64_t length64 = static_cast<uint64_t>(length);
     664        ASSERT(static_cast<double>(length64) == length);
     665        return JSValue::encode(slowJoin(*exec, thisObject, jsSeparator, length64));
     666    }
     667
     668    return JSValue::encode(fastJoin(*exec, thisObject, jsSeparator->view(exec).get(), length));
    601669}
    602670
  • trunk/Source/JavaScriptCore/runtime/JSArray.h

    r203143 r203147  
    354354}
    355355
     356ALWAYS_INLINE double toLength(ExecState* exec, JSObject* obj)
     357{
     358    if (isJSArray(obj))
     359        return jsCast<JSArray*>(obj)->length();
     360
     361    VM& vm = exec->vm();
     362    JSValue lengthValue = obj->get(exec, vm.propertyNames->length);
     363    if (UNLIKELY(vm.exception()))
     364        return PNaN;
     365    return lengthValue.toLength(exec);
     366}
     367
    356368} // namespace JSC
    357369
  • trunk/Source/WTF/ChangeLog

    r203143 r203147  
     12016-07-12  Benjamin Poulain  <bpoulain@apple.com>
     2
     3        [JSC] Array.prototype.join() fails some conformance tests
     4        https://bugs.webkit.org/show_bug.cgi?id=159657
     5
     6        Reviewed by Saam Barati.
     7
     8        * wtf/text/AtomicString.cpp:
     9        (WTF::AtomicString::number):
     10        * wtf/text/AtomicString.h:
     11
    1122016-07-12  Commit Queue  <commit-queue@webkit.org>
    213
  • trunk/Source/WTF/wtf/text/AtomicString.cpp

    r203143 r203147  
    11/*
    2  * Copyright (C) 2004-2008, 2013-2014 Apple Inc. All rights reserved.
     2 * Copyright (C) 2004-2008, 2013-2014, 2016 Apple Inc. All rights reserved.
    33 * Copyright (C) 2010 Patrick Gansterer <paroga@paroga.com>
    44 * Copyright (C) 2012 Google Inc. All rights reserved.
     
    9393}
    9494
     95AtomicString AtomicString::number(unsigned long number)
     96{
     97    return numberToStringUnsigned<AtomicString>(number);
     98}
     99
     100AtomicString AtomicString::number(unsigned long long number)
     101{
     102    return numberToStringUnsigned<AtomicString>(number);
     103}
     104
    95105AtomicString AtomicString::number(double number)
    96106{
  • trunk/Source/WTF/wtf/text/AtomicString.h

    r203143 r203147  
    106106    WTF_EXPORT_STRING_API static AtomicString number(int);
    107107    WTF_EXPORT_STRING_API static AtomicString number(unsigned);
     108    WTF_EXPORT_STRING_API static AtomicString number(unsigned long);
     109    WTF_EXPORT_STRING_API static AtomicString number(unsigned long long);
    108110    WTF_EXPORT_STRING_API static AtomicString number(double);
    109111    // If we need more overloads of the number function, we can add all the others that String has, but these seem to do for now.
Note: See TracChangeset for help on using the changeset viewer.