diff --git a/JSTests/stress/poly-proto-typed-array-case.js b/JSTests/stress/poly-proto-typed-array-case.js new file mode 100644 index 0000000000000..bafb1511b5df1 --- /dev/null +++ b/JSTests/stress/poly-proto-typed-array-case.js @@ -0,0 +1,18 @@ +const typedArray = new Uint8Array(); +let key = '0.1'; +Object.prototype[key] = undefined; + +function bar() { + function Foo() {} + + for (let i = 0; i < 100; i++) { + let foo = new Foo(); + typedArray.__proto__ = foo; + let {x} = foo; + } + typedArray[key]; +} + +for (let i = 0; i < 100; i++) { + bar(); +} diff --git a/JSTests/stress/typed-array-canonical.js b/JSTests/stress/typed-array-canonical.js new file mode 100644 index 0000000000000..680a047918eb7 --- /dev/null +++ b/JSTests/stress/typed-array-canonical.js @@ -0,0 +1,22 @@ +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error('bad value: ' + actual); +} + +Object.prototype["Infinity"] = 42; +Object.prototype["infinity"] = 42; +Object.prototype["NaN"] = 42; +Object.prototype["nan"] = 42; +Object.prototype["-Infinity"] = 42; +Object.prototype["-infinity"] = 42; +Object.prototype["-NaN"] = 42; +Object.prototype["-nan"] = 42; +var array = new Uint8Array(42); +shouldBe(array["Infinity"], undefined); +shouldBe(array["infinity"], 42); +shouldBe(array["NaN"], undefined); +shouldBe(array["nan"], 42); +shouldBe(array["-Infinity"], undefined); +shouldBe(array["-infinity"], 42); +shouldBe(array["-NaN"], 42); +shouldBe(array["-nan"], 42); diff --git a/Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.cpp b/Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.cpp index 7c351fa796cd2..db4e9b4af435b 100644 --- a/Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.cpp +++ b/Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.cpp @@ -264,7 +264,7 @@ ObjectPropertyCondition generateCondition( } template -ObjectPropertyConditionSet generateConditions(JSGlobalObject* globalObject, Structure* structure, JSObject* prototype, const Functor& functor) +ObjectPropertyConditionSet generateConditions(JSGlobalObject* globalObject, Structure* structure, JSObject* prototype, UniquedStringImpl* uid, const Functor& functor) { Vector conditions; @@ -286,6 +286,12 @@ ObjectPropertyConditionSet generateConditions(JSGlobalObject* globalObject, Stru // https://bugs.webkit.org/show_bug.cgi?id=177721 return ObjectPropertyConditionSet::invalid(); } + + // TypedArray has an ability to stop [[Prototype]] traversing for numeric index string (e.g. "0.1"). + // If we found it, then traverse should stop for Unset case. + // https://262.ecma-international.org/9.0/#_ref_2826 + if (!prototype && isTypedArrayType(structure->typeInfo().type()) && uid && isCanonicalNumericIndexString(uid)) + break; JSValue value = structure->prototypeForLookup(globalObject); @@ -333,7 +339,7 @@ ObjectPropertyConditionSet generateConditionsForPropertyMiss( VM& vm, JSCell* owner, JSGlobalObject* globalObject, Structure* headStructure, UniquedStringImpl* uid) { return generateConditions( - globalObject, headStructure, nullptr, + globalObject, headStructure, nullptr, uid, [&](auto& conditions, JSObject* object, Structure* structure) -> bool { ObjectPropertyCondition result = generateCondition(vm, owner, object, structure, uid, PropertyCondition::Absence, Concurrency::MainThread); @@ -348,7 +354,7 @@ ObjectPropertyConditionSet generateConditionsForPropertySetterMiss( VM& vm, JSCell* owner, JSGlobalObject* globalObject, Structure* headStructure, UniquedStringImpl* uid) { return generateConditions( - globalObject, headStructure, nullptr, + globalObject, headStructure, nullptr, uid, [&](auto& conditions, JSObject* object, Structure* structure) -> bool { ObjectPropertyCondition result = generateCondition(vm, owner, object, structure, uid, PropertyCondition::AbsenceOfSetEffect, Concurrency::MainThread); @@ -362,7 +368,7 @@ ObjectPropertyConditionSet generateConditionsForPropertySetterMiss( ObjectPropertyConditionSet generateConditionsForIndexedMiss(VM& vm, JSCell* owner, JSGlobalObject* globalObject, Structure* headStructure) { return generateConditions( - globalObject, headStructure, nullptr, + globalObject, headStructure, nullptr, nullptr, [&](auto& conditions, JSObject* object, Structure* structure) -> bool { ObjectPropertyCondition result = generateCondition(vm, owner, object, structure, nullptr, PropertyCondition::AbsenceOfIndexedProperties, Concurrency::MainThread); @@ -378,7 +384,7 @@ ObjectPropertyConditionSet generateConditionsForPrototypePropertyHit( UniquedStringImpl* uid) { return generateConditions( - globalObject, headStructure, prototype, + globalObject, headStructure, prototype, uid, [&](auto& conditions, JSObject* object, Structure* structure) -> bool { PropertyCondition::Kind kind = object == prototype ? PropertyCondition::Presence : PropertyCondition::Absence; @@ -396,7 +402,7 @@ ObjectPropertyConditionSet generateConditionsForPrototypePropertyHitCustom( UniquedStringImpl* uid, unsigned attributes) { return generateConditions( - globalObject, headStructure, prototype, + globalObject, headStructure, prototype, uid, [&](auto& conditions, JSObject* object, Structure* structure) -> bool { auto kind = PropertyCondition::Absence; if (object == prototype) { @@ -446,7 +452,7 @@ ObjectPropertyConditionSet generateConditionsForInstanceOf( if (ObjectPropertyConditionSetInternal::verbose) dataLog("Searching for prototype ", JSValue(prototype), " starting with structure ", RawPointer(headStructure), " with shouldHit = ", shouldHit, "\n"); ObjectPropertyConditionSet result = generateConditions( - globalObject, headStructure, shouldHit ? prototype : nullptr, + globalObject, headStructure, shouldHit ? prototype : nullptr, nullptr, [&](auto& conditions, JSObject* object, Structure* structure) -> bool { if (ObjectPropertyConditionSetInternal::verbose) dataLog("Encountered object: ", RawPointer(object), "\n"); @@ -474,7 +480,7 @@ ObjectPropertyConditionSet generateConditionsForInstanceOf( ObjectPropertyConditionSet generateConditionsForPrototypeEquivalenceConcurrently( VM& vm, JSGlobalObject* globalObject, Structure* headStructure, JSObject* prototype, UniquedStringImpl* uid) { - return generateConditions(globalObject, headStructure, prototype, + return generateConditions(globalObject, headStructure, prototype, uid, [&](auto& conditions, JSObject* object, Structure* structure) -> bool { PropertyCondition::Kind kind = object == prototype ? PropertyCondition::Equivalence : PropertyCondition::Absence; @@ -490,7 +496,7 @@ ObjectPropertyConditionSet generateConditionsForPropertyMissConcurrently( VM& vm, JSGlobalObject* globalObject, Structure* headStructure, UniquedStringImpl* uid) { return generateConditions( - globalObject, headStructure, nullptr, + globalObject, headStructure, nullptr, uid, [&](auto& conditions, JSObject* object, Structure* structure) -> bool { ObjectPropertyCondition result = generateCondition(vm, nullptr, object, structure, uid, PropertyCondition::Absence, Concurrency::ConcurrentThread); if (!result) @@ -504,7 +510,7 @@ ObjectPropertyConditionSet generateConditionsForPropertySetterMissConcurrently( VM& vm, JSGlobalObject* globalObject, Structure* headStructure, UniquedStringImpl* uid) { return generateConditions( - globalObject, headStructure, nullptr, + globalObject, headStructure, nullptr, uid, [&](auto& conditions, JSObject* object, Structure* structure) -> bool { ObjectPropertyCondition result = generateCondition(vm, nullptr, object, structure, uid, PropertyCondition::AbsenceOfSetEffect, Concurrency::ConcurrentThread); @@ -522,7 +528,7 @@ ObjectPropertyCondition generateConditionForSelfEquivalence( } // Current might be null. Structure can't be null. -static std::optional prepareChainForCaching(JSGlobalObject* globalObject, JSCell* current, Structure* structure, JSObject* target) +static std::optional prepareChainForCaching(JSGlobalObject* globalObject, JSCell* current, Structure* structure, UniquedStringImpl* propertyName, JSObject* target) { ASSERT(structure); VM& vm = globalObject->vm(); @@ -555,6 +561,14 @@ static std::optional prepareChainForCaching(JSGloba break; } + // TypedArray has an ability to stop [[Prototype]] traversing for numeric index string (e.g. "0.1"). + // If we found it, then traverse should stop for Unset case. + // https://262.ecma-international.org/9.0/#_ref_2826 + if (!target && propertyName && isTypedArrayType(structure->typeInfo().type()) && isCanonicalNumericIndexString(propertyName)) { + found = true; + break; + } + // We only have poly proto if we need to access our prototype via // the poly proto protocol. If the slot base is the only poly proto // thing in the chain, and we have a cache hit on it, then we're not @@ -584,20 +598,20 @@ static std::optional prepareChainForCaching(JSGloba return result; } -std::optional prepareChainForCaching(JSGlobalObject* globalObject, JSCell* base, JSObject* target) +std::optional prepareChainForCaching(JSGlobalObject* globalObject, JSCell* base, UniquedStringImpl* propertyName, JSObject* target) { - return prepareChainForCaching(globalObject, base, base->structure(), target); + return prepareChainForCaching(globalObject, base, base->structure(), propertyName, target); } -std::optional prepareChainForCaching(JSGlobalObject* globalObject, JSCell* base, const PropertySlot& slot) +std::optional prepareChainForCaching(JSGlobalObject* globalObject, JSCell* base, UniquedStringImpl* propertyName, const PropertySlot& slot) { JSObject* target = slot.isUnset() ? nullptr : slot.slotBase(); - return prepareChainForCaching(globalObject, base, target); + return prepareChainForCaching(globalObject, base, propertyName, target); } -std::optional prepareChainForCaching(JSGlobalObject* globalObject, Structure* baseStructure, JSObject* target) +std::optional prepareChainForCaching(JSGlobalObject* globalObject, Structure* baseStructure, UniquedStringImpl* propertyName, JSObject* target) { - return prepareChainForCaching(globalObject, nullptr, baseStructure, target); + return prepareChainForCaching(globalObject, nullptr, baseStructure, propertyName, target); } } // namespace JSC diff --git a/Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.h b/Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.h index fda95cd766a6e..ee4c51ba68bae 100644 --- a/Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.h +++ b/Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.h @@ -194,8 +194,8 @@ struct PrototypeChainCachingStatus { bool flattenedDictionary; }; -std::optional prepareChainForCaching(JSGlobalObject*, JSCell* base, const PropertySlot&); -std::optional prepareChainForCaching(JSGlobalObject*, JSCell* base, JSObject* target); -std::optional prepareChainForCaching(JSGlobalObject*, Structure* base, JSObject* target); +std::optional prepareChainForCaching(JSGlobalObject*, JSCell* base, UniquedStringImpl*, const PropertySlot&); +std::optional prepareChainForCaching(JSGlobalObject*, JSCell* base, UniquedStringImpl*, JSObject* target); +std::optional prepareChainForCaching(JSGlobalObject*, Structure* base, UniquedStringImpl*, JSObject* target); } // namespace JSC diff --git a/Source/JavaScriptCore/bytecode/PolyProtoAccessChain.cpp b/Source/JavaScriptCore/bytecode/PolyProtoAccessChain.cpp index 57444f8d25404..f1ea8915235c7 100644 --- a/Source/JavaScriptCore/bytecode/PolyProtoAccessChain.cpp +++ b/Source/JavaScriptCore/bytecode/PolyProtoAccessChain.cpp @@ -30,13 +30,13 @@ namespace JSC { -RefPtr PolyProtoAccessChain::tryCreate(JSGlobalObject* globalObject, JSCell* base, const PropertySlot& slot) +RefPtr PolyProtoAccessChain::tryCreate(JSGlobalObject* globalObject, JSCell* base, CacheableIdentifier propertyName, const PropertySlot& slot) { JSObject* target = slot.isUnset() ? nullptr : slot.slotBase(); - return tryCreate(globalObject, base, target); + return tryCreate(globalObject, base, propertyName, target); } -RefPtr PolyProtoAccessChain::tryCreate(JSGlobalObject* globalObject, JSCell* base, JSObject* target) +RefPtr PolyProtoAccessChain::tryCreate(JSGlobalObject* globalObject, JSCell* base, CacheableIdentifier propertyName, JSObject* target) { JSCell* current = base; @@ -67,6 +67,14 @@ RefPtr PolyProtoAccessChain::tryCreate(JSGlobalObject* glo break; } + // TypedArray has an ability to stop [[Prototype]] traversing for numeric index string (e.g. "0.1"). + // If we found it, then traverse should stop for Unset case. + // https://262.ecma-international.org/9.0/#_ref_2826 + if (!target && isTypedArrayType(structure->typeInfo().type()) && isCanonicalNumericIndexString(propertyName.uid())) { + found = true; + break; + } + JSValue prototype = structure->prototypeForLookup(globalObject, current); if (prototype.isNull()) break; diff --git a/Source/JavaScriptCore/bytecode/PolyProtoAccessChain.h b/Source/JavaScriptCore/bytecode/PolyProtoAccessChain.h index 107e4bf8772df..579757e62a62d 100644 --- a/Source/JavaScriptCore/bytecode/PolyProtoAccessChain.h +++ b/Source/JavaScriptCore/bytecode/PolyProtoAccessChain.h @@ -25,6 +25,7 @@ #pragma once +#include "CacheableIdentifier.h" #include "StructureID.h" #include "VM.h" #include @@ -41,8 +42,8 @@ class Structure; class PolyProtoAccessChain final : public ThreadSafeRefCounted { public: // Returns nullptr when invalid. - static RefPtr tryCreate(JSGlobalObject*, JSCell* base, const PropertySlot&); - static RefPtr tryCreate(JSGlobalObject*, JSCell* base, JSObject* target); + static RefPtr tryCreate(JSGlobalObject*, JSCell* base, CacheableIdentifier, const PropertySlot&); + static RefPtr tryCreate(JSGlobalObject*, JSCell* base, CacheableIdentifier, JSObject* target); const FixedVector& chain() const { return m_chain; } diff --git a/Source/JavaScriptCore/bytecode/Repatch.cpp b/Source/JavaScriptCore/bytecode/Repatch.cpp index b91555ea812f6..857c9325a87fa 100644 --- a/Source/JavaScriptCore/bytecode/Repatch.cpp +++ b/Source/JavaScriptCore/bytecode/Repatch.cpp @@ -436,7 +436,7 @@ static InlineCacheAction tryCacheGetBy(JSGlobalObject* globalObject, CodeBlock* // need to be informed if the custom goes away since we cache the // constant function pointer. - if (!prepareChainForCaching(globalObject, slot.slotBase(), slot.slotBase())) + if (!prepareChainForCaching(globalObject, slot.slotBase(), propertyName.uid(), slot.slotBase())) return GiveUpOnCache; } @@ -457,7 +457,7 @@ static InlineCacheAction tryCacheGetBy(JSGlobalObject* globalObject, CodeBlock* // If a kind is GetByKind::ByIdDirect or GetByKind::PrivateName, we do not need to investigate prototype chains further. // Cacheability just depends on the head structure. if (kind != GetByKind::ByIdDirect && !isPrivate) { - auto cacheStatus = prepareChainForCaching(globalObject, baseCell, slot); + auto cacheStatus = prepareChainForCaching(globalObject, baseCell, propertyName.uid(), slot); if (!cacheStatus) return GiveUpOnCache; @@ -467,10 +467,10 @@ static InlineCacheAction tryCacheGetBy(JSGlobalObject* globalObject, CodeBlock* } if (cacheStatus->usesPolyProto) { - prototypeAccessChain = PolyProtoAccessChain::tryCreate(globalObject, baseCell, slot); + prototypeAccessChain = PolyProtoAccessChain::tryCreate(globalObject, baseCell, propertyName, slot); if (!prototypeAccessChain) return GiveUpOnCache; - RELEASE_ASSERT(slot.isCacheableCustom() || prototypeAccessChain->slotBaseStructure(vm, structure)->get(vm, propertyName.uid()) == offset); + ASSERT(slot.isCacheableCustom() || prototypeAccessChain->slotBaseStructure(vm, structure)->get(vm, propertyName.uid()) == offset); } else { // We use ObjectPropertyConditionSet instead for faster accesses. prototypeAccessChain = nullptr; @@ -674,7 +674,7 @@ static InlineCacheAction tryCacheArrayGetByVal(JSGlobalObject* globalObject, Cod return GiveUpOnCache; // FIXME: prepareChainForCaching is conservative. We should have another function which only cares about information related to this IC. - auto cacheStatus = prepareChainForCaching(globalObject, base, nullptr); + auto cacheStatus = prepareChainForCaching(globalObject, base, nullptr, nullptr); if (!cacheStatus) return GiveUpOnCache; @@ -917,12 +917,12 @@ static InlineCacheAction tryCachePutBy(JSGlobalObject* globalObject, CodeBlock* RefPtr prototypeAccessChain; ObjectPropertyConditionSet conditionSet; if (putKind == PutKind::NotDirect) { - auto cacheStatus = prepareChainForCaching(globalObject, baseCell, nullptr); + auto cacheStatus = prepareChainForCaching(globalObject, baseCell, propertyName.uid(), nullptr); if (!cacheStatus) return GiveUpOnCache; if (cacheStatus->usesPolyProto) { - prototypeAccessChain = PolyProtoAccessChain::tryCreate(globalObject, baseCell, nullptr); + prototypeAccessChain = PolyProtoAccessChain::tryCreate(globalObject, baseCell, propertyName, nullptr); if (!prototypeAccessChain) return GiveUpOnCache; } else { @@ -951,13 +951,13 @@ static InlineCacheAction tryCachePutBy(JSGlobalObject* globalObject, CodeBlock* // We need to do this even if we're a self custom, since we must disallow dictionaries // because we need to be informed if the custom goes away since we cache the constant // function pointer. - auto cacheStatus = prepareChainForCaching(globalObject, baseCell, slot.base()); + auto cacheStatus = prepareChainForCaching(globalObject, baseCell, propertyName.uid(), slot.base()); if (!cacheStatus) return GiveUpOnCache; if (slot.base() != baseValue) { if (cacheStatus->usesPolyProto) { - prototypeAccessChain = PolyProtoAccessChain::tryCreate(globalObject, baseCell, slot.base()); + prototypeAccessChain = PolyProtoAccessChain::tryCreate(globalObject, baseCell, propertyName, slot.base()); if (!prototypeAccessChain) return GiveUpOnCache; } else { @@ -979,14 +979,14 @@ static InlineCacheAction tryCachePutBy(JSGlobalObject* globalObject, CodeBlock* PropertyOffset offset = slot.cachedOffset(); if (slot.base() != baseValue) { - auto cacheStatus = prepareChainForCaching(globalObject, baseCell, slot.base()); + auto cacheStatus = prepareChainForCaching(globalObject, baseCell, propertyName.uid(), slot.base()); if (!cacheStatus) return GiveUpOnCache; if (cacheStatus->flattenedDictionary) return RetryCacheLater; if (cacheStatus->usesPolyProto) { - prototypeAccessChain = PolyProtoAccessChain::tryCreate(globalObject, baseCell, slot.base()); + prototypeAccessChain = PolyProtoAccessChain::tryCreate(globalObject, baseCell, propertyName, slot.base()); if (!prototypeAccessChain) return GiveUpOnCache; unsigned attributes; @@ -1282,33 +1282,33 @@ static InlineCacheAction tryCacheInBy( } if (slot.slotBase() != base) { - auto cacheStatus = prepareChainForCaching(globalObject, base, slot); + auto cacheStatus = prepareChainForCaching(globalObject, base, propertyName.uid(), slot); if (!cacheStatus) return GiveUpOnCache; if (cacheStatus->flattenedDictionary) return RetryCacheLater; if (cacheStatus->usesPolyProto) { - prototypeAccessChain = PolyProtoAccessChain::tryCreate(globalObject, base, slot); + prototypeAccessChain = PolyProtoAccessChain::tryCreate(globalObject, base, propertyName, slot); if (!prototypeAccessChain) return GiveUpOnCache; - RELEASE_ASSERT(slot.isCacheableCustom() || prototypeAccessChain->slotBaseStructure(vm, structure)->get(vm, propertyName.uid()) == slot.cachedOffset()); + ASSERT(slot.isCacheableCustom() || prototypeAccessChain->slotBaseStructure(vm, structure)->get(vm, propertyName.uid()) == slot.cachedOffset()); } else { prototypeAccessChain = nullptr; conditionSet = generateConditionsForPrototypePropertyHit( vm, codeBlock, globalObject, structure, slot.slotBase(), ident.impl()); if (!conditionSet.isValid()) return GiveUpOnCache; - RELEASE_ASSERT(slot.isCacheableCustom() || conditionSet.slotBaseCondition().offset() == slot.cachedOffset()); + ASSERT(slot.isCacheableCustom() || conditionSet.slotBaseCondition().offset() == slot.cachedOffset()); } } } else { - auto cacheStatus = prepareChainForCaching(globalObject, base, nullptr); + auto cacheStatus = prepareChainForCaching(globalObject, base, propertyName.uid(), nullptr); if (!cacheStatus) return GiveUpOnCache; if (cacheStatus->usesPolyProto) { - prototypeAccessChain = PolyProtoAccessChain::tryCreate(globalObject, base, slot); + prototypeAccessChain = PolyProtoAccessChain::tryCreate(globalObject, base, propertyName, slot); if (!prototypeAccessChain) return GiveUpOnCache; } else { @@ -1531,7 +1531,7 @@ static InlineCacheAction tryCacheInstanceOf( } else if (structure->prototypeQueriesAreCacheable()) { // FIXME: Teach this to do poly proto. // https://bugs.webkit.org/show_bug.cgi?id=185663 - prepareChainForCaching(globalObject, value, wasFound ? prototype : nullptr); + prepareChainForCaching(globalObject, value, nullptr, wasFound ? prototype : nullptr); ObjectPropertyConditionSet conditionSet = generateConditionsForInstanceOf( vm, codeBlock, globalObject, structure, prototype, wasFound); diff --git a/Source/JavaScriptCore/llint/LLIntSlowPaths.cpp b/Source/JavaScriptCore/llint/LLIntSlowPaths.cpp index 13968515bd0ac..38853ccb0c069 100644 --- a/Source/JavaScriptCore/llint/LLIntSlowPaths.cpp +++ b/Source/JavaScriptCore/llint/LLIntSlowPaths.cpp @@ -763,7 +763,7 @@ static void setupGetByIdPrototypeCache(JSGlobalObject* globalObject, VM& vm, Cod structure->flattenDictionaryStructure(vm, jsCast(baseCell)); } - prepareChainForCaching(globalObject, baseCell, slot); + prepareChainForCaching(globalObject, baseCell, ident.impl(), slot); ObjectPropertyConditionSet conditions; if (slot.isUnset()) diff --git a/Source/JavaScriptCore/runtime/JSGenericTypedArrayViewInlines.h b/Source/JavaScriptCore/runtime/JSGenericTypedArrayViewInlines.h index 29d9f7aa8bcab..739ee94a05d3c 100644 --- a/Source/JavaScriptCore/runtime/JSGenericTypedArrayViewInlines.h +++ b/Source/JavaScriptCore/runtime/JSGenericTypedArrayViewInlines.h @@ -377,7 +377,7 @@ bool JSGenericTypedArrayView::getOwnPropertySlot( return getOwnPropertySlotByIndex(thisObject, globalObject, index.value(), slot); } - if (isCanonicalNumericIndexString(propertyName)) + if (isCanonicalNumericIndexString(propertyName.uid())) return false; return Base::getOwnPropertySlot(thisObject, globalObject, propertyName, slot); @@ -395,7 +395,7 @@ bool JSGenericTypedArrayView::put( return putByIndex(thisObject, globalObject, index.value(), value, slot.isStrictMode()); } - if (isCanonicalNumericIndexString(propertyName)) { + if (isCanonicalNumericIndexString(propertyName.uid())) { // Cases like '-0', '1.1', etc. are still obliged to give the RHS a chance to throw. toNativeFromValue(globalObject, value); return true; @@ -445,7 +445,7 @@ bool JSGenericTypedArrayView::defineOwnProperty( return true; } - if (isCanonicalNumericIndexString(propertyName)) + if (isCanonicalNumericIndexString(propertyName.uid())) return typeError(globalObject, scope, shouldThrow, "Attempting to store canonical numeric string property on a typed array"_s); RELEASE_AND_RETURN(scope, Base::defineOwnProperty(thisObject, globalObject, propertyName, descriptor, shouldThrow)); @@ -462,7 +462,7 @@ bool JSGenericTypedArrayView::deleteProperty( return deletePropertyByIndex(thisObject, globalObject, index.value()); } - if (isCanonicalNumericIndexString(propertyName)) + if (isCanonicalNumericIndexString(propertyName.uid())) return true; return Base::deleteProperty(thisObject, globalObject, propertyName, slot); diff --git a/Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp b/Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp index f077b9eecaeb9..539455f843a1e 100644 --- a/Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp +++ b/Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp @@ -359,52 +359,53 @@ static double jsStrDecimalLiteral(const CharType*& data, const CharType* end) return PNaN; } -template -static double toDouble(const CharType* characters, unsigned size) +template +static double toDouble(WTF::Span characters) { - const CharType* endCharacters = characters + size; + const auto* rawCharacters = characters.data(); + const auto* endRawCharacters = rawCharacters + characters.size(); // Skip leading white space. - for (; characters < endCharacters; ++characters) { - if (!isStrWhiteSpace(*characters)) + for (; rawCharacters < endRawCharacters; ++rawCharacters) { + if (!isStrWhiteSpace(*rawCharacters)) break; } // Empty string. - if (characters == endCharacters) + if (rawCharacters == endRawCharacters) return 0.0; double number; - if (characters[0] == '0' && characters + 2 < endCharacters) { - if ((characters[1] | 0x20) == 'x' && isASCIIHexDigit(characters[2])) - number = jsHexIntegerLiteral(characters, endCharacters); - else if ((characters[1] | 0x20) == 'o' && isASCIIOctalDigit(characters[2])) - number = jsOctalIntegerLiteral(characters, endCharacters); - else if ((characters[1] | 0x20) == 'b' && isASCIIBinaryDigit(characters[2])) - number = jsBinaryIntegerLiteral(characters, endCharacters); + if (rawCharacters[0] == '0' && rawCharacters + 2 < endRawCharacters) { + if ((rawCharacters[1] | 0x20) == 'x' && isASCIIHexDigit(rawCharacters[2])) + number = jsHexIntegerLiteral(rawCharacters, endRawCharacters); + else if ((rawCharacters[1] | 0x20) == 'o' && isASCIIOctalDigit(rawCharacters[2])) + number = jsOctalIntegerLiteral(rawCharacters, endRawCharacters); + else if ((rawCharacters[1] | 0x20) == 'b' && isASCIIBinaryDigit(rawCharacters[2])) + number = jsBinaryIntegerLiteral(rawCharacters, endRawCharacters); else - number = jsStrDecimalLiteral(characters, endCharacters); + number = jsStrDecimalLiteral(rawCharacters, endRawCharacters); } else - number = jsStrDecimalLiteral(characters, endCharacters); + number = jsStrDecimalLiteral(rawCharacters, endRawCharacters); // Allow trailing white space. - for (; characters < endCharacters; ++characters) { - if (!isStrWhiteSpace(*characters)) + for (; rawCharacters < endRawCharacters; ++rawCharacters) { + if (!isStrWhiteSpace(*rawCharacters)) break; } - if (characters != endCharacters) + if (rawCharacters != endRawCharacters) return PNaN; return number; } // See ecma-262 6th 11.8.3 -double jsToNumber(StringView s) +template +static ALWAYS_INLINE double jsToNumber(WTF::Span characters) { - unsigned size = s.length(); - - if (size == 1) { - UChar c = s[0]; + auto* rawCharacters = characters.data(); + if (characters.size() == 1) { + auto c = rawCharacters[0]; if (isASCIIDigit(c)) return c - '0'; if (isStrWhiteSpace(c)) @@ -412,9 +413,23 @@ double jsToNumber(StringView s) return PNaN; } + if (characters.size() == 2 && rawCharacters[0] == '-') { + auto c = rawCharacters[1]; + if (c == '0') + return -0.0; + if (isASCIIDigit(c)) + return -static_cast(c - '0'); + return PNaN; + } + + return toDouble(characters); +} + +double jsToNumber(StringView s) +{ if (s.is8Bit()) - return toDouble(s.characters8(), size); - return toDouble(s.characters16(), size); + return jsToNumber(s.span8()); + return jsToNumber(s.span16()); } static double parseFloat(StringView s) diff --git a/Source/JavaScriptCore/runtime/JSObjectInlines.h b/Source/JavaScriptCore/runtime/JSObjectInlines.h index 27b3995db37ce..5c86102fc7a85 100644 --- a/Source/JavaScriptCore/runtime/JSObjectInlines.h +++ b/Source/JavaScriptCore/runtime/JSObjectInlines.h @@ -165,7 +165,7 @@ ALWAYS_INLINE bool JSObject::getNonIndexPropertySlot(JSGlobalObject* globalObjec return false; if (object->type() == ProxyObjectType && slot.internalMethodType() == PropertySlot::InternalMethodType::HasProperty) return false; - if (isTypedArrayType(object->type()) && isCanonicalNumericIndexString(propertyName)) + if (isTypedArrayType(object->type()) && isCanonicalNumericIndexString(propertyName.uid())) return false; } JSValue prototype; diff --git a/Source/JavaScriptCore/runtime/JSStringJoiner.cpp b/Source/JavaScriptCore/runtime/JSStringJoiner.cpp index eed9134ae737a..debaf8ce827cb 100644 --- a/Source/JavaScriptCore/runtime/JSStringJoiner.cpp +++ b/Source/JavaScriptCore/runtime/JSStringJoiner.cpp @@ -27,6 +27,7 @@ #include "JSStringJoiner.h" #include "JSCJSValueInlines.h" +#include "wtf/Span.h" namespace JSC { @@ -34,6 +35,13 @@ JSStringJoiner::~JSStringJoiner() { } +template +static inline void appendStringToData(OutputCharacterType*& data, WTF::Span separator) +{ + StringImpl::copyCharacters(data, separator.data(), separator.size()); + data += separator.size(); +} + template static inline void appendStringToData(CharacterType*& data, StringView string) { @@ -41,12 +49,12 @@ static inline void appendStringToData(CharacterType*& data, StringView string) data += string.length(); } -template -static inline String joinStrings(const Vector& strings, StringView separator, unsigned joinedLength) +template +static inline String joinStrings(const Vector& strings, WTF::Span separator, unsigned joinedLength) { ASSERT(joinedLength); - CharacterType* data; + OutputCharacterType* data; String result = StringImpl::tryCreateUninitialized(joinedLength, data); if (UNLIKELY(result.isNull())) return result; @@ -55,13 +63,13 @@ static inline String joinStrings(const Vector& s unsigned size = strings.size(); - switch (separator.length()) { + switch (separator.size()) { case 0: for (unsigned i = 1; i < size; ++i) appendStringToData(data, strings[i].view); break; case 1: { - CharacterType separatorCharacter = separator[0]; + OutputCharacterType separatorCharacter = separator.data()[0]; for (unsigned i = 1; i < size; ++i) { *data++ = separatorCharacter; appendStringToData(data, strings[i].view); @@ -74,7 +82,7 @@ static inline String joinStrings(const Vector& s appendStringToData(data, strings[i].view); } } - ASSERT(data == result.characters() + joinedLength); + ASSERT(data == result.characters() + joinedLength); return result; } @@ -113,9 +121,13 @@ JSValue JSStringJoiner::join(JSGlobalObject* globalObject) String result; if (m_isAll8Bit) - result = joinStrings(m_strings, m_separator, length); - else - result = joinStrings(m_strings, m_separator, length); + result = joinStrings(m_strings, m_separator.span8(), length); + else { + if (m_separator.is8Bit()) + result = joinStrings(m_strings, m_separator.span8(), length); + else + result = joinStrings(m_strings, m_separator.span16(), length); + } if (result.isNull()) return throwOutOfMemoryError(globalObject, scope); diff --git a/Source/JavaScriptCore/runtime/PropertyName.h b/Source/JavaScriptCore/runtime/PropertyName.h index be28a0b7b42d5..f05aef0c73295 100644 --- a/Source/JavaScriptCore/runtime/PropertyName.h +++ b/Source/JavaScriptCore/runtime/PropertyName.h @@ -29,6 +29,7 @@ #include "JSGlobalObjectFunctions.h" #include "PrivateName.h" #include +#include namespace JSC { @@ -131,22 +132,50 @@ ALWAYS_INLINE std::optional parseIndex(PropertyName propertyName) return parseIndex(*uid); } +template +ALWAYS_INLINE std::optional fastIsCanonicalNumericIndexString(WTF::Span characters) +{ + auto* rawCharacters = characters.data(); + auto length = characters.size(); + ASSERT(length >= 1); + auto first = rawCharacters[0]; + if (length == 1) + return isASCIIDigit(first); + auto second = rawCharacters[1]; + if (first == '-') { + // -Infinity case should go to the slow path. -NaN cannot exist since it becomes NaN. + if (!isASCIIDigit(second) && (length != strlen("-Infinity") || second != 'I')) + return false; + if (length == 2) // Including -0, and it should be accepted. + return true; + } else if (!isASCIIDigit(first)) { + // Infinity and NaN should go to the slow path. + if (!(length == strlen("Infinity") && first == 'I') && !(length == strlen("NaN") && first == 'N')) + return false; + } + return std::nullopt; +} + // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-canonicalnumericindexstring -ALWAYS_INLINE bool isCanonicalNumericIndexString(const PropertyName& propertyName) +ALWAYS_INLINE bool isCanonicalNumericIndexString(UniquedStringImpl* propertyName) { - StringImpl* property = propertyName.uid(); - if (!property) + if (!propertyName) return false; - if (property->isSymbol()) + if (propertyName->isSymbol()) return false; - if (equal(property, "-0"_s)) - return true; - double index = jsToNumber(property); + if (!propertyName->length()) + return false; + + auto fastResult = propertyName->is8Bit() + ? fastIsCanonicalNumericIndexString(propertyName->span8()) + : fastIsCanonicalNumericIndexString(propertyName->span16()); + if (fastResult) + return *fastResult; + + double index = jsToNumber(propertyName); NumberToStringBuffer buffer; const char* indexString = WTF::numberToString(index, buffer); - if (!equal(property, indexString)) - return false; - return true; + return equal(propertyName, indexString); } } // namespace JSC diff --git a/Source/JavaScriptCore/runtime/StructureRareData.cpp b/Source/JavaScriptCore/runtime/StructureRareData.cpp index d72317481c0d4..d79e2f5ad4d3d 100644 --- a/Source/JavaScriptCore/runtime/StructureRareData.cpp +++ b/Source/JavaScriptCore/runtime/StructureRareData.cpp @@ -27,6 +27,7 @@ #include "StructureRareData.h" #include "AdaptiveInferredPropertyValueWatchpointBase.h" +#include "CacheableIdentifierInlines.h" #include "CachedSpecialPropertyAdaptiveStructureWatchpoint.h" #include "JSImmutableButterfly.h" #include "JSObjectInlines.h" @@ -155,7 +156,7 @@ void StructureRareData::cacheSpecialPropertySlow(JSGlobalObject* globalObject, V // This will not create a condition for the current structure but that is good because we know that property // is not on the ownStructure so we will transisition if one is added and this cache will no longer be used. - auto cacheStatus = prepareChainForCaching(globalObject, ownStructure, slot.slotBase()); + auto cacheStatus = prepareChainForCaching(globalObject, ownStructure, uid, slot.slotBase()); if (!cacheStatus) { giveUpOnSpecialPropertyCache(key); return; @@ -168,7 +169,7 @@ void StructureRareData::cacheSpecialPropertySlow(JSGlobalObject* globalObject, V return; } - auto cacheStatus = prepareChainForCaching(globalObject, ownStructure, nullptr); + auto cacheStatus = prepareChainForCaching(globalObject, ownStructure, uid, nullptr); if (!cacheStatus) { giveUpOnSpecialPropertyCache(key); return; diff --git a/Source/WTF/wtf/text/StringImpl.h b/Source/WTF/wtf/text/StringImpl.h index b91e2410f8af4..c1b98ec7f2f14 100644 --- a/Source/WTF/wtf/text/StringImpl.h +++ b/Source/WTF/wtf/text/StringImpl.h @@ -304,6 +304,8 @@ class StringImpl : private StringImplShape { bool is8Bit() const { return m_hashAndFlags & s_hashFlag8BitBuffer; } ALWAYS_INLINE const LChar* characters8() const { ASSERT(is8Bit()); return m_data8; } ALWAYS_INLINE const UChar* characters16() const { ASSERT(!is8Bit() || isEmpty()); return m_data16; } + ALWAYS_INLINE Span span8() const { return { characters8(), length() }; } + ALWAYS_INLINE Span span16() const { return { characters16(), length() }; } template const CharacterType* characters() const; diff --git a/Source/WTF/wtf/text/StringView.h b/Source/WTF/wtf/text/StringView.h index 7356a2fc28490..deaba4b3c79ea 100644 --- a/Source/WTF/wtf/text/StringView.h +++ b/Source/WTF/wtf/text/StringView.h @@ -96,7 +96,10 @@ class StringView final { bool is8Bit() const; const LChar* characters8() const; const UChar* characters16() const; + Span span8() const { return { characters8(), length() }; } + Span span16() const { return { characters16(), length() }; } + unsigned hash() const; // Return characters8() or characters16() depending on CharacterType. @@ -193,8 +196,11 @@ class StringView final { // Clients should use StringView(ASCIILiteral) or StringView::fromLatin1() instead. explicit StringView(const char*); + UChar unsafeCharacterAt(unsigned index) const; + friend bool equal(StringView, StringView); friend WTF_EXPORT_PRIVATE bool equalRespectingNullity(StringView, StringView); + friend size_t findCommon(StringView haystack, StringView needle, unsigned start); void initialize(const LChar*, unsigned length); void initialize(const UChar*, unsigned length); @@ -534,6 +540,14 @@ inline StringView StringView::substring(unsigned start, unsigned length) const } inline UChar StringView::characterAt(unsigned index) const +{ + RELEASE_ASSERT(index < length()); + if (is8Bit()) + return characters8()[index]; + return characters16()[index]; +} + +inline UChar StringView::unsafeCharacterAt(unsigned index) const { ASSERT(index < length()); if (is8Bit()) @@ -1008,7 +1022,7 @@ inline auto StringView::CodeUnits::Iterator::operator++() -> Iterator& inline UChar StringView::CodeUnits::Iterator::operator*() const { - return m_stringView[m_index]; + return m_stringView.unsafeCharacterAt(m_index); } inline bool StringView::CodeUnits::Iterator::operator==(const Iterator& other) const @@ -1161,9 +1175,10 @@ inline size_t findCommon(StringView haystack, StringView needle, unsigned start) unsigned needleLength = needle.length(); if (needleLength == 1) { + UChar firstCharacter = needle.unsafeCharacterAt(0); if (haystack.is8Bit()) - return WTF::find(haystack.characters8(), haystack.length(), needle[0], start); - return WTF::find(haystack.characters16(), haystack.length(), needle[0], start); + return WTF::find(haystack.characters8(), haystack.length(), firstCharacter, start); + return WTF::find(haystack.characters16(), haystack.length(), firstCharacter, start); } if (start > haystack.length())