REGRESSION (r181458): Heap use-after-free in JSSetIterator destructor
https://bugs.webkit.org/show_bug.cgi?id=142696
Reviewed and tweaked by Geoffrey Garen.
Source/JavaScriptCore:
Before r142556, JSSetIterator::destroy was not defined.
So accidentally MapData::const_iterator in JSSet was never destroyed.
But it had non trivial destructor, decrementing MapData->m_iteratorCount.
After r142556, JSSetIterator::destroy works.
It correctly destruct MapData::const_iterator and m_iteratorCount partially works.
But JSSetIterator::~JSSetIterator requires owned JSSet since it mutates MapData->m_iteratorCount.
It is guaranteed that JSSet is live since JSSetIterator has a reference to JSSet
and marks it in visitChildren (WriteBarrier<Unknown>).
However, the order of destructions is not guaranteed in GC-ed system.
Consider the following case,
allocate JSSet and subsequently allocate JSSetIterator.
And they resides in the separated MarkedBlock, <1> and <2>.
JSSet<1> <- JSSetIterator<2>
And after that, when performing GC, Marker decides that the above 2 objects are not marked.
And Marker also decides MarkedBlocks <1> and <2> can be sweeped.
First Sweeper sweep <1>, destruct JSSet<1> and free MarkedBlock<1>.
Second Sweeper sweep <2>, attempt to destruct JSSetIterator<2>.
However, JSSetIterator<2>'s destructor,
JSSetIterator::~JSSetIterator requires live JSSet<1>, it causes use-after-free.
In this patch, we introduce WeakGCMap into JSMap/JSSet to track live iterators.
When packing the removed elements in JSSet/JSMap, we apply the change to all live
iterators tracked by WeakGCMap.
WeakGCMap can only track JSCell since they are managed by GC.
So we drop JSSet/JSMap C++ style iterators. Instead of C++ style iterator, this patch
introduces JS style iterator signatures into C++ class IteratorData.
If we need to iterate over JSMap/JSSet, use JSSetIterator/JSMapIterator instead of using
IteratorData directly.
Patch by Yusuke Suzuki <utatane.tea@gmail.com> on 2015-03-24
* runtime/JSMap.cpp:
(JSC::JSMap::destroy):
* runtime/JSMap.h:
(JSC::JSMap::JSMap):
(JSC::JSMap::begin): Deleted.
(JSC::JSMap::end): Deleted.
* runtime/JSMapIterator.cpp:
(JSC::JSMapIterator::destroy):
* runtime/JSMapIterator.h:
(JSC::JSMapIterator::next):
(JSC::JSMapIterator::nextKeyValue):
(JSC::JSMapIterator::iteratorData):
(JSC::JSMapIterator::JSMapIterator):
* runtime/JSSet.cpp:
(JSC::JSSet::destroy):
* runtime/JSSet.h:
(JSC::JSSet::JSSet):
(JSC::JSSet::begin): Deleted.
(JSC::JSSet::end): Deleted.
* runtime/JSSetIterator.cpp:
(JSC::JSSetIterator::destroy):
* runtime/JSSetIterator.h:
(JSC::JSSetIterator::next):
(JSC::JSSetIterator::iteratorData):
(JSC::JSSetIterator::JSSetIterator):
* runtime/MapData.h:
(JSC::MapDataImpl::IteratorData::finish):
(JSC::MapDataImpl::IteratorData::isFinished):
(JSC::MapDataImpl::shouldPack):
(JSC::JSIterator>::MapDataImpl):
(JSC::JSIterator>::KeyType::KeyType):
(JSC::JSIterator>::IteratorData::IteratorData):
(JSC::JSIterator>::IteratorData::next):
(JSC::JSIterator>::IteratorData::ensureSlot):
(JSC::JSIterator>::IteratorData::applyMapDataPatch):
(JSC::JSIterator>::IteratorData::refreshCursor):
(JSC::MapDataImpl::const_iterator::key): Deleted.
(JSC::MapDataImpl::const_iterator::value): Deleted.
(JSC::MapDataImpl::const_iterator::operator++): Deleted.
(JSC::MapDataImpl::const_iterator::finish): Deleted.
(JSC::MapDataImpl::const_iterator::atEnd): Deleted.
(JSC::MapDataImpl::begin): Deleted.
(JSC::MapDataImpl::end): Deleted.
(JSC::MapDataImpl<Entry>::MapDataImpl): Deleted.
(JSC::MapDataImpl<Entry>::clear): Deleted.
(JSC::MapDataImpl<Entry>::KeyType::KeyType): Deleted.
(JSC::MapDataImpl<Entry>::const_iterator::internalIncrement): Deleted.
(JSC::MapDataImpl<Entry>::const_iterator::ensureSlot): Deleted.
(JSC::MapDataImpl<Entry>::const_iterator::const_iterator): Deleted.
(JSC::MapDataImpl<Entry>::const_iterator::~const_iterator): Deleted.
(JSC::MapDataImpl<Entry>::const_iterator::operator): Deleted.
(JSC::=): Deleted.
* runtime/MapDataInlines.h:
(JSC::JSIterator>::clear):
(JSC::JSIterator>::find):
(JSC::JSIterator>::contains):
(JSC::JSIterator>::add):
(JSC::JSIterator>::set):
(JSC::JSIterator>::get):
(JSC::JSIterator>::remove):
(JSC::JSIterator>::replaceAndPackBackingStore):
(JSC::JSIterator>::replaceBackingStore):
(JSC::JSIterator>::ensureSpaceForAppend):
(JSC::JSIterator>::visitChildren):
(JSC::JSIterator>::copyBackingStore):
(JSC::JSIterator>::applyMapDataPatch):
(JSC::MapDataImpl<Entry>::find): Deleted.
(JSC::MapDataImpl<Entry>::contains): Deleted.
(JSC::MapDataImpl<Entry>::add): Deleted.
(JSC::MapDataImpl<Entry>::set): Deleted.
(JSC::MapDataImpl<Entry>::get): Deleted.
(JSC::MapDataImpl<Entry>::remove): Deleted.
(JSC::MapDataImpl<Entry>::replaceAndPackBackingStore): Deleted.
(JSC::MapDataImpl<Entry>::replaceBackingStore): Deleted.
(JSC::MapDataImpl<Entry>::ensureSpaceForAppend): Deleted.
(JSC::MapDataImpl<Entry>::visitChildren): Deleted.
(JSC::MapDataImpl<Entry>::copyBackingStore): Deleted.
* runtime/MapPrototype.cpp:
(JSC::mapProtoFuncForEach):
* runtime/SetPrototype.cpp:
(JSC::setProtoFuncForEach):
* runtime/WeakGCMap.h:
(JSC::WeakGCMap::forEach):
* tests/stress/modify-map-during-iteration.js: Added.
(testValue):
(identityPairs):
(.set if):
(var):
(set map):
* tests/stress/modify-set-during-iteration.js: Added.
(testValue):
(set forEach):
(set delete):
Source/WebCore:
Use JSSetIterator/JSMapIterator to iterate over JSSet and JSMap.
Patch by Yusuke Suzuki <utatane.tea@gmail.com> on 2015-03-24
* ForwardingHeaders/runtime/JSMapIterator.h: Added.
* ForwardingHeaders/runtime/JSSetIterator.h: Added.
* bindings/js/SerializedScriptValue.cpp:
(WebCore::CloneSerializer::serialize):
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@181922 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog
index 30f18d3..dc756c8 100644
--- a/Source/JavaScriptCore/ChangeLog
+++ b/Source/JavaScriptCore/ChangeLog
@@ -1,3 +1,140 @@
+2015-03-24 Yusuke Suzuki <utatane.tea@gmail.com>
+
+ REGRESSION (r181458): Heap use-after-free in JSSetIterator destructor
+ https://bugs.webkit.org/show_bug.cgi?id=142696
+
+ Reviewed and tweaked by Geoffrey Garen.
+
+ Before r142556, JSSetIterator::destroy was not defined.
+ So accidentally MapData::const_iterator in JSSet was never destroyed.
+ But it had non trivial destructor, decrementing MapData->m_iteratorCount.
+
+ After r142556, JSSetIterator::destroy works.
+ It correctly destruct MapData::const_iterator and m_iteratorCount partially works.
+ But JSSetIterator::~JSSetIterator requires owned JSSet since it mutates MapData->m_iteratorCount.
+
+ It is guaranteed that JSSet is live since JSSetIterator has a reference to JSSet
+ and marks it in visitChildren (WriteBarrier<Unknown>).
+ However, the order of destructions is not guaranteed in GC-ed system.
+
+ Consider the following case,
+ allocate JSSet and subsequently allocate JSSetIterator.
+ And they resides in the separated MarkedBlock, <1> and <2>.
+
+ JSSet<1> <- JSSetIterator<2>
+
+ And after that, when performing GC, Marker decides that the above 2 objects are not marked.
+ And Marker also decides MarkedBlocks <1> and <2> can be sweeped.
+
+ First Sweeper sweep <1>, destruct JSSet<1> and free MarkedBlock<1>.
+ Second Sweeper sweep <2>, attempt to destruct JSSetIterator<2>.
+ However, JSSetIterator<2>'s destructor,
+ JSSetIterator::~JSSetIterator requires live JSSet<1>, it causes use-after-free.
+
+ In this patch, we introduce WeakGCMap into JSMap/JSSet to track live iterators.
+ When packing the removed elements in JSSet/JSMap, we apply the change to all live
+ iterators tracked by WeakGCMap.
+
+ WeakGCMap can only track JSCell since they are managed by GC.
+ So we drop JSSet/JSMap C++ style iterators. Instead of C++ style iterator, this patch
+ introduces JS style iterator signatures into C++ class IteratorData.
+ If we need to iterate over JSMap/JSSet, use JSSetIterator/JSMapIterator instead of using
+ IteratorData directly.
+
+ * runtime/JSMap.cpp:
+ (JSC::JSMap::destroy):
+ * runtime/JSMap.h:
+ (JSC::JSMap::JSMap):
+ (JSC::JSMap::begin): Deleted.
+ (JSC::JSMap::end): Deleted.
+ * runtime/JSMapIterator.cpp:
+ (JSC::JSMapIterator::destroy):
+ * runtime/JSMapIterator.h:
+ (JSC::JSMapIterator::next):
+ (JSC::JSMapIterator::nextKeyValue):
+ (JSC::JSMapIterator::iteratorData):
+ (JSC::JSMapIterator::JSMapIterator):
+ * runtime/JSSet.cpp:
+ (JSC::JSSet::destroy):
+ * runtime/JSSet.h:
+ (JSC::JSSet::JSSet):
+ (JSC::JSSet::begin): Deleted.
+ (JSC::JSSet::end): Deleted.
+ * runtime/JSSetIterator.cpp:
+ (JSC::JSSetIterator::destroy):
+ * runtime/JSSetIterator.h:
+ (JSC::JSSetIterator::next):
+ (JSC::JSSetIterator::iteratorData):
+ (JSC::JSSetIterator::JSSetIterator):
+ * runtime/MapData.h:
+ (JSC::MapDataImpl::IteratorData::finish):
+ (JSC::MapDataImpl::IteratorData::isFinished):
+ (JSC::MapDataImpl::shouldPack):
+ (JSC::JSIterator>::MapDataImpl):
+ (JSC::JSIterator>::KeyType::KeyType):
+ (JSC::JSIterator>::IteratorData::IteratorData):
+ (JSC::JSIterator>::IteratorData::next):
+ (JSC::JSIterator>::IteratorData::ensureSlot):
+ (JSC::JSIterator>::IteratorData::applyMapDataPatch):
+ (JSC::JSIterator>::IteratorData::refreshCursor):
+ (JSC::MapDataImpl::const_iterator::key): Deleted.
+ (JSC::MapDataImpl::const_iterator::value): Deleted.
+ (JSC::MapDataImpl::const_iterator::operator++): Deleted.
+ (JSC::MapDataImpl::const_iterator::finish): Deleted.
+ (JSC::MapDataImpl::const_iterator::atEnd): Deleted.
+ (JSC::MapDataImpl::begin): Deleted.
+ (JSC::MapDataImpl::end): Deleted.
+ (JSC::MapDataImpl<Entry>::MapDataImpl): Deleted.
+ (JSC::MapDataImpl<Entry>::clear): Deleted.
+ (JSC::MapDataImpl<Entry>::KeyType::KeyType): Deleted.
+ (JSC::MapDataImpl<Entry>::const_iterator::internalIncrement): Deleted.
+ (JSC::MapDataImpl<Entry>::const_iterator::ensureSlot): Deleted.
+ (JSC::MapDataImpl<Entry>::const_iterator::const_iterator): Deleted.
+ (JSC::MapDataImpl<Entry>::const_iterator::~const_iterator): Deleted.
+ (JSC::MapDataImpl<Entry>::const_iterator::operator): Deleted.
+ (JSC::=): Deleted.
+ * runtime/MapDataInlines.h:
+ (JSC::JSIterator>::clear):
+ (JSC::JSIterator>::find):
+ (JSC::JSIterator>::contains):
+ (JSC::JSIterator>::add):
+ (JSC::JSIterator>::set):
+ (JSC::JSIterator>::get):
+ (JSC::JSIterator>::remove):
+ (JSC::JSIterator>::replaceAndPackBackingStore):
+ (JSC::JSIterator>::replaceBackingStore):
+ (JSC::JSIterator>::ensureSpaceForAppend):
+ (JSC::JSIterator>::visitChildren):
+ (JSC::JSIterator>::copyBackingStore):
+ (JSC::JSIterator>::applyMapDataPatch):
+ (JSC::MapDataImpl<Entry>::find): Deleted.
+ (JSC::MapDataImpl<Entry>::contains): Deleted.
+ (JSC::MapDataImpl<Entry>::add): Deleted.
+ (JSC::MapDataImpl<Entry>::set): Deleted.
+ (JSC::MapDataImpl<Entry>::get): Deleted.
+ (JSC::MapDataImpl<Entry>::remove): Deleted.
+ (JSC::MapDataImpl<Entry>::replaceAndPackBackingStore): Deleted.
+ (JSC::MapDataImpl<Entry>::replaceBackingStore): Deleted.
+ (JSC::MapDataImpl<Entry>::ensureSpaceForAppend): Deleted.
+ (JSC::MapDataImpl<Entry>::visitChildren): Deleted.
+ (JSC::MapDataImpl<Entry>::copyBackingStore): Deleted.
+ * runtime/MapPrototype.cpp:
+ (JSC::mapProtoFuncForEach):
+ * runtime/SetPrototype.cpp:
+ (JSC::setProtoFuncForEach):
+ * runtime/WeakGCMap.h:
+ (JSC::WeakGCMap::forEach):
+ * tests/stress/modify-map-during-iteration.js: Added.
+ (testValue):
+ (identityPairs):
+ (.set if):
+ (var):
+ (set map):
+ * tests/stress/modify-set-during-iteration.js: Added.
+ (testValue):
+ (set forEach):
+ (set delete):
+
2015-03-24 Mark Lam <mark.lam@apple.com>
The ExecutionTimeLimit test should use its own JSGlobalContextRef.
diff --git a/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj b/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj
index c23122d..0a77bea 100644
--- a/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj
+++ b/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj
@@ -1305,7 +1305,7 @@
A74DEF93182D991400522C22 /* MapIteratorPrototype.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A74DEF8D182D991400522C22 /* MapIteratorPrototype.cpp */; };
A74DEF94182D991400522C22 /* MapIteratorPrototype.h in Headers */ = {isa = PBXBuildFile; fileRef = A74DEF8E182D991400522C22 /* MapIteratorPrototype.h */; };
A74DEF95182D991400522C22 /* JSMapIterator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A74DEF8F182D991400522C22 /* JSMapIterator.cpp */; };
- A74DEF96182D991400522C22 /* JSMapIterator.h in Headers */ = {isa = PBXBuildFile; fileRef = A74DEF90182D991400522C22 /* JSMapIterator.h */; };
+ A74DEF96182D991400522C22 /* JSMapIterator.h in Headers */ = {isa = PBXBuildFile; fileRef = A74DEF90182D991400522C22 /* JSMapIterator.h */; settings = {ATTRIBUTES = (Private, ); }; };
A75706DE118A2BCF0057F88F /* JITArithmetic32_64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A75706DD118A2BCF0057F88F /* JITArithmetic32_64.cpp */; };
A75EE9B218AAB7E200AAD043 /* BuiltinNames.h in Headers */ = {isa = PBXBuildFile; fileRef = A75EE9B018AAB7E200AAD043 /* BuiltinNames.h */; };
A76140CD182982CB00750624 /* ArgumentsIteratorConstructor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A76140C7182982CB00750624 /* ArgumentsIteratorConstructor.cpp */; };
@@ -1352,7 +1352,7 @@
A790DD6D182F499700588807 /* SetIteratorPrototype.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A790DD67182F499700588807 /* SetIteratorPrototype.cpp */; };
A790DD6E182F499700588807 /* SetIteratorPrototype.h in Headers */ = {isa = PBXBuildFile; fileRef = A790DD68182F499700588807 /* SetIteratorPrototype.h */; };
A790DD6F182F499700588807 /* JSSetIterator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A790DD69182F499700588807 /* JSSetIterator.cpp */; };
- A790DD70182F499700588807 /* JSSetIterator.h in Headers */ = {isa = PBXBuildFile; fileRef = A790DD6A182F499700588807 /* JSSetIterator.h */; };
+ A790DD70182F499700588807 /* JSSetIterator.h in Headers */ = {isa = PBXBuildFile; fileRef = A790DD6A182F499700588807 /* JSSetIterator.h */; settings = {ATTRIBUTES = (Private, ); }; };
A7986D5717A0BB1E00A95DD0 /* DFGEdgeUsesStructure.h in Headers */ = {isa = PBXBuildFile; fileRef = A7986D5617A0BB1E00A95DD0 /* DFGEdgeUsesStructure.h */; settings = {ATTRIBUTES = (Private, ); }; };
A7A4AE0817973B26005612B1 /* MacroAssemblerX86Common.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7A4AE0717973B26005612B1 /* MacroAssemblerX86Common.cpp */; };
A7A4AE1017973B4D005612B1 /* JITStubsX86Common.h in Headers */ = {isa = PBXBuildFile; fileRef = A7A4AE0C17973B4D005612B1 /* JITStubsX86Common.h */; };
diff --git a/Source/JavaScriptCore/runtime/JSMap.cpp b/Source/JavaScriptCore/runtime/JSMap.cpp
index 441006d..352648b 100644
--- a/Source/JavaScriptCore/runtime/JSMap.cpp
+++ b/Source/JavaScriptCore/runtime/JSMap.cpp
@@ -27,6 +27,7 @@
#include "JSMap.h"
#include "JSCJSValueInlines.h"
+#include "JSMapIterator.h"
#include "MapDataInlines.h"
#include "SlotVisitorInlines.h"
#include "StructureInlines.h"
@@ -37,7 +38,8 @@
void JSMap::destroy(JSCell* cell)
{
- jsCast<JSMap*>(cell)->~JSMap();
+ JSMap* thisObject = jsCast<JSMap*>(cell);
+ thisObject->JSMap::~JSMap();
}
void JSMap::visitChildren(JSCell* cell, SlotVisitor& visitor)
diff --git a/Source/JavaScriptCore/runtime/JSMap.h b/Source/JavaScriptCore/runtime/JSMap.h
index 4afd0bb..837f7e3 100644
--- a/Source/JavaScriptCore/runtime/JSMap.h
+++ b/Source/JavaScriptCore/runtime/JSMap.h
@@ -32,10 +32,14 @@
namespace JSC {
+class JSMapIterator;
+
class JSMap : public JSDestructibleObject {
public:
typedef JSDestructibleObject Base;
+ friend class JSMapIterator;
+
// Our marking functions expect Entry to maintain this layout, and have all
// fields be WriteBarrier<Unknown>
class Entry {
@@ -82,7 +86,7 @@
}
};
- typedef MapDataImpl<Entry> MapData;
+ typedef MapDataImpl<Entry, JSMapIterator> MapData;
DECLARE_EXPORT_INFO;
@@ -103,16 +107,6 @@
return create(exec->vm(), structure);
}
- typedef MapData::const_iterator const_iterator;
-
- const_iterator begin() const
- {
- return m_mapData.begin();
- }
- const_iterator end() const
- {
- return m_mapData.end();
- }
bool has(ExecState*, JSValue);
size_t size(ExecState*);
JSValue get(ExecState*, JSValue);
@@ -123,7 +117,7 @@
private:
JSMap(VM& vm, Structure* structure)
: Base(vm, structure)
- , m_mapData()
+ , m_mapData(vm)
{
}
diff --git a/Source/JavaScriptCore/runtime/JSMapIterator.cpp b/Source/JavaScriptCore/runtime/JSMapIterator.cpp
index e9d7d42..885362b 100644
--- a/Source/JavaScriptCore/runtime/JSMapIterator.cpp
+++ b/Source/JavaScriptCore/runtime/JSMapIterator.cpp
@@ -29,6 +29,7 @@
#include "JSCJSValueInlines.h"
#include "JSCellInlines.h"
#include "JSMap.h"
+#include "MapDataInlines.h"
#include "SlotVisitorInlines.h"
#include "StructureInlines.h"
@@ -44,7 +45,8 @@
void JSMapIterator::destroy(JSCell* cell)
{
- jsCast<JSMapIterator*>(cell)->~JSMapIterator();
+ JSMapIterator* thisObject = jsCast<JSMapIterator*>(cell);
+ thisObject->JSMapIterator::~JSMapIterator();
}
void JSMapIterator::visitChildren(JSCell* cell, SlotVisitor& visitor)
diff --git a/Source/JavaScriptCore/runtime/JSMapIterator.h b/Source/JavaScriptCore/runtime/JSMapIterator.h
index ab87b3a..cc2ae86 100644
--- a/Source/JavaScriptCore/runtime/JSMapIterator.h
+++ b/Source/JavaScriptCore/runtime/JSMapIterator.h
@@ -57,16 +57,27 @@
bool next(CallFrame* callFrame, JSValue& value)
{
- if (!m_iterator.ensureSlot())
+ WTF::KeyValuePair<JSValue, JSValue> pair;
+ if (!m_iterator.next(pair))
return false;
if (m_kind == MapIterateValue)
- value = m_iterator.value();
+ value = pair.value;
else if (m_kind == MapIterateKey)
- value = m_iterator.key();
+ value = pair.key;
else
- value = createPair(callFrame, m_iterator.key(), m_iterator.value());
- ++m_iterator;
+ value = createPair(callFrame, pair.key, pair.value);
+ return true;
+ }
+
+ bool nextKeyValue(JSValue& key, JSValue& value)
+ {
+ WTF::KeyValuePair<JSValue, JSValue> pair;
+ if (!m_iterator.next(pair))
+ return false;
+
+ key = pair.key;
+ value = pair.value;
return true;
}
@@ -79,21 +90,26 @@
JSValue iteratedValue() const { return m_map.get(); }
JSMapIterator* clone(ExecState*);
+ JSMap::MapData::IteratorData* iteratorData()
+ {
+ return &m_iterator;
+ }
+
private:
JSMapIterator(VM& vm, Structure* structure, JSMap* iteratedObject, MapIterationKind kind)
: Base(vm, structure)
- , m_iterator(iteratedObject->begin())
+ , m_iterator(iteratedObject->m_mapData.createIteratorData(this))
, m_kind(kind)
{
}
static void destroy(JSCell*);
- void finishCreation(VM&, JSMap*);
+ JS_EXPORT_PRIVATE void finishCreation(VM&, JSMap*);
JSValue createPair(CallFrame*, JSValue, JSValue);
static void visitChildren(JSCell*, SlotVisitor&);
WriteBarrier<JSMap> m_map;
- JSMap::const_iterator m_iterator;
+ JSMap::MapData::IteratorData m_iterator;
MapIterationKind m_kind;
};
diff --git a/Source/JavaScriptCore/runtime/JSSet.cpp b/Source/JavaScriptCore/runtime/JSSet.cpp
index 8fc5637..d875345 100644
--- a/Source/JavaScriptCore/runtime/JSSet.cpp
+++ b/Source/JavaScriptCore/runtime/JSSet.cpp
@@ -27,6 +27,7 @@
#include "JSSet.h"
#include "JSCJSValueInlines.h"
+#include "JSSetIterator.h"
#include "MapDataInlines.h"
#include "SlotVisitorInlines.h"
#include "StructureInlines.h"
@@ -37,7 +38,8 @@
void JSSet::destroy(JSCell* cell)
{
- jsCast<JSSet*>(cell)->~JSSet();
+ JSSet* thisObject = jsCast<JSSet*>(cell);
+ thisObject->JSSet::~JSSet();
}
void JSSet::visitChildren(JSCell* cell, SlotVisitor& visitor)
diff --git a/Source/JavaScriptCore/runtime/JSSet.h b/Source/JavaScriptCore/runtime/JSSet.h
index f052abe..e55362e 100644
--- a/Source/JavaScriptCore/runtime/JSSet.h
+++ b/Source/JavaScriptCore/runtime/JSSet.h
@@ -32,10 +32,14 @@
namespace JSC {
+class JSSetIterator;
+
class JSSet : public JSDestructibleObject {
public:
typedef JSDestructibleObject Base;
+ friend class JSSetIterator;
+
// Our marking functions expect Entry to maintain this layout, and have all
// fields be WriteBarrier<Unknown>
class Entry {
@@ -78,7 +82,7 @@
}
};
- typedef MapDataImpl<Entry> SetData;
+ typedef MapDataImpl<Entry, JSSetIterator> SetData;
DECLARE_EXPORT_INFO;
@@ -99,16 +103,6 @@
return create(exec->vm(), structure);
}
- typedef SetData::const_iterator const_iterator;
-
- const_iterator begin() const
- {
- return m_setData.begin();
- }
- const_iterator end() const
- {
- return m_setData.end();
- }
bool has(ExecState*, JSValue);
size_t size(ExecState*);
JS_EXPORT_PRIVATE void add(ExecState*, JSValue);
@@ -118,7 +112,7 @@
private:
JSSet(VM& vm, Structure* structure)
: Base(vm, structure)
- , m_setData()
+ , m_setData(vm)
{
}
diff --git a/Source/JavaScriptCore/runtime/JSSetIterator.cpp b/Source/JavaScriptCore/runtime/JSSetIterator.cpp
index 5ecac12..3a0ddbe 100644
--- a/Source/JavaScriptCore/runtime/JSSetIterator.cpp
+++ b/Source/JavaScriptCore/runtime/JSSetIterator.cpp
@@ -29,6 +29,7 @@
#include "JSCJSValueInlines.h"
#include "JSCellInlines.h"
#include "JSSet.h"
+#include "MapDataInlines.h"
#include "SlotVisitorInlines.h"
#include "StructureInlines.h"
@@ -52,7 +53,8 @@
void JSSetIterator::destroy(JSCell* cell)
{
- jsCast<JSSetIterator*>(cell)->~JSSetIterator();
+ JSSetIterator* thisObject = jsCast<JSSetIterator*>(cell);
+ thisObject->JSSetIterator::~JSSetIterator();
}
JSValue JSSetIterator::createPair(CallFrame* callFrame, JSValue key, JSValue value)
diff --git a/Source/JavaScriptCore/runtime/JSSetIterator.h b/Source/JavaScriptCore/runtime/JSSetIterator.h
index b063c51..6dad6b1 100644
--- a/Source/JavaScriptCore/runtime/JSSetIterator.h
+++ b/Source/JavaScriptCore/runtime/JSSetIterator.h
@@ -58,13 +58,13 @@
bool next(CallFrame* callFrame, JSValue& value)
{
- if (!m_iterator.ensureSlot())
+ WTF::KeyValuePair<JSValue, JSValue> pair;
+ if (!m_iterator.next(pair))
return false;
if (m_kind == SetIterateValue || m_kind == SetIterateKey)
- value = m_iterator.key();
+ value = pair.key;
else
- value = createPair(callFrame, m_iterator.key(), m_iterator.key());
- ++m_iterator;
+ value = createPair(callFrame, pair.key, pair.key);
return true;
}
@@ -77,21 +77,26 @@
JSValue iteratedValue() const { return m_set.get(); }
JSSetIterator* clone(ExecState*);
+ JSSet::SetData::IteratorData* iteratorData()
+ {
+ return &m_iterator;
+ }
+
private:
JSSetIterator(VM& vm, Structure* structure, JSSet* iteratedObject, SetIterationKind kind)
: Base(vm, structure)
- , m_iterator(iteratedObject->begin())
+ , m_iterator(iteratedObject->m_setData.createIteratorData(this))
, m_kind(kind)
{
}
static void destroy(JSCell*);
- void finishCreation(VM&, JSSet*);
- JSValue createPair(CallFrame*, JSValue, JSValue);
+ JS_EXPORT_PRIVATE void finishCreation(VM&, JSSet*);
+ JS_EXPORT_PRIVATE JSValue createPair(CallFrame*, JSValue, JSValue);
static void visitChildren(JSCell*, SlotVisitor&);
WriteBarrier<JSSet> m_set;
- JSSet::const_iterator m_iterator;
+ JSSet::SetData::IteratorData m_iterator;
SetIterationKind m_kind;
};
diff --git a/Source/JavaScriptCore/runtime/MapData.h b/Source/JavaScriptCore/runtime/MapData.h
index 6f3bac2..a8af1ab 100644
--- a/Source/JavaScriptCore/runtime/MapData.h
+++ b/Source/JavaScriptCore/runtime/MapData.h
@@ -27,45 +27,57 @@
#define MapData_h
#include "JSCell.h"
+#include "WeakGCMapInlines.h"
#include <wtf/HashFunctions.h>
#include <wtf/HashMap.h>
#include <wtf/MathExtras.h>
+#include <wtf/RefCounted.h>
+#include <wtf/RefPtr.h>
+#include <wtf/Vector.h>
namespace JSC {
class ExecState;
+class VM;
-template<typename Entry>
+template<typename Entry, typename JSIterator>
class MapDataImpl {
public:
enum : int32_t {
minimumMapSize = 8
};
- struct const_iterator {
- const_iterator(const MapDataImpl*);
- ~const_iterator();
- const WTF::KeyValuePair<JSValue, JSValue> operator*() const;
- JSValue key() const { RELEASE_ASSERT(!atEnd()); return m_mapData->m_entries[m_index].key().get(); }
- JSValue value() const { RELEASE_ASSERT(!atEnd()); return m_mapData->m_entries[m_index].value().get(); }
- void operator++() { ASSERT(!atEnd()); internalIncrement(); }
- static const_iterator end(const MapDataImpl*);
- bool operator!=(const const_iterator& other);
- bool operator==(const const_iterator& other);
- void finish() { m_index = std::numeric_limits<int32_t>::max(); }
+ class IteratorData {
+ public:
+ friend class MapDataImpl;
- bool ensureSlot();
+ IteratorData(const MapDataImpl*);
+ bool next(WTF::KeyValuePair<JSValue, JSValue>&);
+
+ void didRemoveEntry(int32_t index)
+ {
+ if (m_index <= index)
+ return;
+ --m_index;
+ }
+
+ void didRemoveAllEntries()
+ {
+ m_index = 0;
+ }
+
+ void finish()
+ {
+ m_index = -1;
+ }
private:
- // This is a bit gnarly. We use an index of -1 to indicate the
- // "end()" iterator. By casting to unsigned we can immediately
- // test if both iterators are at the end of their iteration.
- // We need this in order to keep the common case (eg. iter != end())
- // fast.
- bool atEnd() const { return static_cast<size_t>(m_index) >= static_cast<size_t>(m_mapData->m_size); }
- void internalIncrement();
+ bool ensureSlot() const;
+ bool isFinished() const { return m_index == -1; }
+ int32_t refreshCursor() const;
+
const MapDataImpl* m_mapData;
- int32_t m_index;
+ mutable int32_t m_index;
};
struct KeyType {
@@ -74,7 +86,7 @@
JSValue value;
};
- MapDataImpl();
+ MapDataImpl(VM&);
void set(ExecState*, JSCell* owner, KeyType, JSValue);
JSValue get(ExecState*, KeyType);
@@ -82,8 +94,7 @@
bool contains(ExecState*, KeyType);
size_t size(ExecState*) const { return m_size - m_deletedCount; }
- const_iterator begin() const { return const_iterator(this); }
- const_iterator end() const { return const_iterator::end(this); }
+ IteratorData createIteratorData(JSIterator*);
void clear();
@@ -104,7 +115,7 @@
ALWAYS_INLINE Entry* add(ExecState*, JSCell* owner, KeyType);
template <typename Map, typename Key> ALWAYS_INLINE Entry* add(ExecState*, JSCell* owner, Map&, Key, KeyType);
- ALWAYS_INLINE bool shouldPack() const { return m_deletedCount && !m_iteratorCount; }
+ ALWAYS_INLINE bool shouldPack() const { return m_deletedCount; }
CheckedBoolean ensureSpaceForAppend(ExecState*, JSCell* owner);
ALWAYS_INLINE void replaceAndPackBackingStore(Entry* destination, int32_t newSize);
@@ -117,35 +128,22 @@
int32_t m_capacity;
int32_t m_size;
int32_t m_deletedCount;
- mutable int32_t m_iteratorCount;
Entry* m_entries;
+ WeakGCMap<JSIterator*, JSIterator> m_iterators;
};
-template<typename Entry>
-ALWAYS_INLINE MapDataImpl<Entry>::MapDataImpl()
+template<typename Entry, typename JSIterator>
+ALWAYS_INLINE MapDataImpl<Entry, JSIterator>::MapDataImpl(VM& vm)
: m_capacity(0)
, m_size(0)
, m_deletedCount(0)
- , m_iteratorCount(0)
, m_entries(nullptr)
+ , m_iterators(vm)
{
}
-template<typename Entry>
-ALWAYS_INLINE void MapDataImpl<Entry>::clear()
-{
- m_cellKeyedTable.clear();
- m_valueKeyedTable.clear();
- m_stringKeyedTable.clear();
- m_symbolKeyedTable.clear();
- m_capacity = 0;
- m_size = 0;
- m_deletedCount = 0;
- m_entries = nullptr;
-}
-
-template<typename Entry>
-ALWAYS_INLINE MapDataImpl<Entry>::KeyType::KeyType(JSValue v)
+template<typename Entry, typename JSIterator>
+ALWAYS_INLINE MapDataImpl<Entry, JSIterator>::KeyType::KeyType(JSValue v)
{
if (!v.isDouble()) {
value = v;
@@ -164,73 +162,45 @@
value = jsNumber(i);
}
-template<typename Entry>
-ALWAYS_INLINE void MapDataImpl<Entry>::const_iterator::internalIncrement()
-{
- Entry* entries = m_mapData->m_entries;
- size_t index = m_index + 1;
- size_t end = m_mapData->m_size;
- while (index < end && !entries[index].key())
- index++;
- m_index = index;
-}
-
-template<typename Entry>
-ALWAYS_INLINE bool MapDataImpl<Entry>::const_iterator::ensureSlot()
-{
- // When an iterator exists outside of host cost it is possible for
- // the containing map to be modified
- Entry* entries = m_mapData->m_entries;
- size_t index = m_index;
- size_t end = m_mapData->m_size;
- if (index < end && entries[index].key())
- return true;
- internalIncrement();
- return static_cast<size_t>(m_index) < end;
-}
-
-template<typename Entry>
-ALWAYS_INLINE MapDataImpl<Entry>::const_iterator::const_iterator(const MapDataImpl<Entry>* mapData)
+template<typename Entry, typename JSIterator>
+ALWAYS_INLINE MapDataImpl<Entry, JSIterator>::IteratorData::IteratorData(const MapDataImpl<Entry, JSIterator>* mapData)
: m_mapData(mapData)
- , m_index(-1)
+ , m_index(0)
{
- internalIncrement();
}
-template<typename Entry>
-ALWAYS_INLINE MapDataImpl<Entry>::const_iterator::~const_iterator()
+template<typename Entry, typename JSIterator>
+ALWAYS_INLINE bool MapDataImpl<Entry, JSIterator>::IteratorData::next(WTF::KeyValuePair<JSValue, JSValue>& pair)
{
- m_mapData->m_iteratorCount--;
-}
-
-template<typename Entry>
-ALWAYS_INLINE const WTF::KeyValuePair<JSValue, JSValue> MapDataImpl<Entry>::const_iterator::operator*() const
-{
- Entry* entry = &m_mapData->m_entries[m_index];
- return WTF::KeyValuePair<JSValue, JSValue>(entry->key().get(), entry->value().get());
-}
-
-template<typename Entry>
-ALWAYS_INLINE auto MapDataImpl<Entry>::const_iterator::end(const MapDataImpl<Entry>* mapData) -> const_iterator
-{
- const_iterator result(mapData);
- result.m_index = -1;
- return result;
-}
-
-template<typename Entry>
-ALWAYS_INLINE bool MapDataImpl<Entry>::const_iterator::operator!=(const const_iterator& other)
-{
- ASSERT(other.m_mapData == m_mapData);
- if (atEnd() && other.atEnd())
+ if (!ensureSlot())
return false;
- return m_index != other.m_index;
+ Entry* entry = &m_mapData->m_entries[m_index];
+ pair = WTF::KeyValuePair<JSValue, JSValue>(entry->key().get(), entry->value().get());
+ m_index += 1;
+ return true;
}
-template<typename Entry>
-ALWAYS_INLINE bool MapDataImpl<Entry>::const_iterator::operator==(const const_iterator& other)
+// This is a bit gnarly. We use an index of -1 to indicate the
+// finished state. By casting to unsigned we can immediately
+// test if both iterators are at the end of their iteration.
+template<typename Entry, typename JSIterator>
+ALWAYS_INLINE bool MapDataImpl<Entry, JSIterator>::IteratorData::ensureSlot() const
{
- return !(*this != other);
+ int32_t index = refreshCursor();
+ return static_cast<size_t>(index) < static_cast<size_t>(m_mapData->m_size);
+}
+
+template<typename Entry, typename JSIterator>
+ALWAYS_INLINE int32_t MapDataImpl<Entry, JSIterator>::IteratorData::refreshCursor() const
+{
+ if (isFinished())
+ return m_index;
+
+ Entry* entries = m_mapData->m_entries;
+ size_t end = m_mapData->m_size;
+ while (static_cast<size_t>(m_index) < end && !entries[m_index].key())
+ m_index++;
+ return m_index;
}
}
diff --git a/Source/JavaScriptCore/runtime/MapDataInlines.h b/Source/JavaScriptCore/runtime/MapDataInlines.h
index fb2c53a..478308992 100644
--- a/Source/JavaScriptCore/runtime/MapDataInlines.h
+++ b/Source/JavaScriptCore/runtime/MapDataInlines.h
@@ -37,8 +37,24 @@
namespace JSC {
-template<typename Entry>
-inline Entry* MapDataImpl<Entry>::find(ExecState* exec, KeyType key)
+template<typename Entry, typename JSIterator>
+inline void MapDataImpl<Entry, JSIterator>::clear()
+{
+ m_cellKeyedTable.clear();
+ m_valueKeyedTable.clear();
+ m_stringKeyedTable.clear();
+ m_symbolKeyedTable.clear();
+ m_capacity = 0;
+ m_size = 0;
+ m_deletedCount = 0;
+ m_entries = nullptr;
+ m_iterators.forEach([](JSIterator* iterator, JSIterator*) {
+ iterator->iteratorData()->didRemoveAllEntries();
+ });
+}
+
+template<typename Entry, typename JSIterator>
+inline Entry* MapDataImpl<Entry, JSIterator>::find(ExecState* exec, KeyType key)
{
if (key.value.isString()) {
auto iter = m_stringKeyedTable.find(asString(key.value)->value(exec).impl());
@@ -65,15 +81,15 @@
return &m_entries[iter->value];
}
-template<typename Entry>
-inline bool MapDataImpl<Entry>::contains(ExecState* exec, KeyType key)
+template<typename Entry, typename JSIterator>
+inline bool MapDataImpl<Entry, JSIterator>::contains(ExecState* exec, KeyType key)
{
return find(exec, key);
}
-template<typename Entry>
+template<typename Entry, typename JSIterator>
template <typename Map, typename Key>
-inline Entry* MapDataImpl<Entry>::add(ExecState* exec, JSCell* owner, Map& map, Key key, KeyType keyValue)
+inline Entry* MapDataImpl<Entry, JSIterator>::add(ExecState* exec, JSCell* owner, Map& map, Key key, KeyType keyValue)
{
typename Map::iterator location = map.find(key);
if (location != map.end())
@@ -90,8 +106,8 @@
return entry;
}
-template<typename Entry>
-inline void MapDataImpl<Entry>::set(ExecState* exec, JSCell* owner, KeyType key, JSValue value)
+template<typename Entry, typename JSIterator>
+inline void MapDataImpl<Entry, JSIterator>::set(ExecState* exec, JSCell* owner, KeyType key, JSValue value)
{
Entry* location = add(exec, owner, key);
if (!location)
@@ -99,8 +115,8 @@
location->setValue(exec->vm(), owner, value);
}
-template<typename Entry>
-inline Entry* MapDataImpl<Entry>::add(ExecState* exec, JSCell* owner, KeyType key)
+template<typename Entry, typename JSIterator>
+inline Entry* MapDataImpl<Entry, JSIterator>::add(ExecState* exec, JSCell* owner, KeyType key)
{
if (key.value.isString())
return add(exec, owner, m_stringKeyedTable, asString(key.value)->value(exec).impl(), key);
@@ -111,16 +127,16 @@
return add(exec, owner, m_valueKeyedTable, JSValue::encode(key.value), key);
}
-template<typename Entry>
-inline JSValue MapDataImpl<Entry>::get(ExecState* exec, KeyType key)
+template<typename Entry, typename JSIterator>
+inline JSValue MapDataImpl<Entry, JSIterator>::get(ExecState* exec, KeyType key)
{
if (Entry* entry = find(exec, key))
return entry->value().get();
return JSValue();
}
-template<typename Entry>
-inline bool MapDataImpl<Entry>::remove(ExecState* exec, KeyType key)
+template<typename Entry, typename JSIterator>
+inline bool MapDataImpl<Entry, JSIterator>::remove(ExecState* exec, KeyType key)
{
int32_t location;
if (key.value.isString()) {
@@ -153,16 +169,20 @@
return true;
}
-template<typename Entry>
-inline void MapDataImpl<Entry>::replaceAndPackBackingStore(Entry* destination, int32_t newCapacity)
+template<typename Entry, typename JSIterator>
+inline void MapDataImpl<Entry, JSIterator>::replaceAndPackBackingStore(Entry* destination, int32_t newCapacity)
{
ASSERT(shouldPack());
int32_t newEnd = 0;
RELEASE_ASSERT(newCapacity > 0);
for (int32_t i = 0; i < m_size; i++) {
Entry& entry = m_entries[i];
- if (!entry.key())
+ if (!entry.key()) {
+ m_iterators.forEach([i](JSIterator* iterator, JSIterator*) {
+ iterator->iteratorData()->didRemoveEntry(i);
+ });
continue;
+ }
ASSERT(newEnd < newCapacity);
destination[newEnd] = entry;
@@ -191,8 +211,8 @@
m_entries = destination;
}
-template<typename Entry>
-inline void MapDataImpl<Entry>::replaceBackingStore(Entry* destination, int32_t newCapacity)
+template<typename Entry, typename JSIterator>
+inline void MapDataImpl<Entry, JSIterator>::replaceBackingStore(Entry* destination, int32_t newCapacity)
{
ASSERT(!shouldPack());
RELEASE_ASSERT(newCapacity > 0);
@@ -202,8 +222,8 @@
m_entries = destination;
}
-template<typename Entry>
-inline CheckedBoolean MapDataImpl<Entry>::ensureSpaceForAppend(ExecState* exec, JSCell* owner)
+template<typename Entry, typename JSIterator>
+inline CheckedBoolean MapDataImpl<Entry, JSIterator>::ensureSpaceForAppend(ExecState* exec, JSCell* owner)
{
if (m_capacity > m_size)
return true;
@@ -224,8 +244,8 @@
return true;
}
-template<typename Entry>
-inline void MapDataImpl<Entry>::visitChildren(JSCell* owner, SlotVisitor& visitor)
+template<typename Entry, typename JSIterator>
+inline void MapDataImpl<Entry, JSIterator>::visitChildren(JSCell* owner, SlotVisitor& visitor)
{
Entry* entries = m_entries;
if (!entries)
@@ -244,8 +264,8 @@
visitor.copyLater(owner, MapBackingStoreCopyToken, entries, capacityInBytes());
}
-template<typename Entry>
-inline void MapDataImpl<Entry>::copyBackingStore(CopyVisitor& visitor, CopyToken token)
+template<typename Entry, typename JSIterator>
+inline void MapDataImpl<Entry, JSIterator>::copyBackingStore(CopyVisitor& visitor, CopyToken token)
{
if (token == MapBackingStoreCopyToken && visitor.checkIfShouldCopy(m_entries)) {
Entry* oldEntries = m_entries;
@@ -258,4 +278,11 @@
}
}
+template<typename Entry, typename JSIterator>
+inline auto MapDataImpl<Entry, JSIterator>::createIteratorData(JSIterator* iterator) -> IteratorData
+{
+ m_iterators.set(iterator, iterator);
+ return IteratorData(this);
+}
+
}
diff --git a/Source/JavaScriptCore/runtime/MapPrototype.cpp b/Source/JavaScriptCore/runtime/MapPrototype.cpp
index 6d7a11d..88df583 100644
--- a/Source/JavaScriptCore/runtime/MapPrototype.cpp
+++ b/Source/JavaScriptCore/runtime/MapPrototype.cpp
@@ -119,22 +119,26 @@
return JSValue::encode(throwTypeError(callFrame, WTF::ASCIILiteral("Map.prototype.forEach called without callback")));
JSValue thisValue = callFrame->argument(1);
VM* vm = &callFrame->vm();
+ JSMapIterator* iterator = JSMapIterator::create(*vm, callFrame->callee()->globalObject()->mapIteratorStructure(), map, MapIterateKeyValue);
+ JSValue key, value;
if (callType == CallTypeJS) {
JSFunction* function = jsCast<JSFunction*>(callBack);
CachedCall cachedCall(callFrame, function, 2);
- for (auto ptr = map->begin(), end = map->end(); ptr != end && !vm->exception(); ++ptr) {
+ while (iterator->nextKeyValue(key, value) && !vm->exception()) {
cachedCall.setThis(thisValue);
- cachedCall.setArgument(0, ptr.value());
- cachedCall.setArgument(1, ptr.key());
+ cachedCall.setArgument(0, value);
+ cachedCall.setArgument(1, key);
cachedCall.call();
}
+ iterator->finish();
} else {
- for (auto ptr = map->begin(), end = map->end(); ptr != end && !vm->exception(); ++ptr) {
+ while (iterator->nextKeyValue(key, value) && !vm->exception()) {
MarkedArgumentBuffer args;
- args.append(ptr.value());
- args.append(ptr.key());
+ args.append(value);
+ args.append(key);
JSC::call(callFrame, callBack, callType, callData, thisValue, args);
}
+ iterator->finish();
}
return JSValue::encode(jsUndefined());
}
diff --git a/Source/JavaScriptCore/runtime/SetPrototype.cpp b/Source/JavaScriptCore/runtime/SetPrototype.cpp
index 6a6c49b..40066ae 100644
--- a/Source/JavaScriptCore/runtime/SetPrototype.cpp
+++ b/Source/JavaScriptCore/runtime/SetPrototype.cpp
@@ -128,20 +128,24 @@
return JSValue::encode(throwTypeError(callFrame, WTF::ASCIILiteral("Set.prototype.forEach called without callback")));
JSValue thisValue = callFrame->argument(1);
VM* vm = &callFrame->vm();
+ JSSetIterator* iterator = JSSetIterator::create(*vm, callFrame->callee()->globalObject()->setIteratorStructure(), set, SetIterateKey);
+ JSValue key;
if (callType == CallTypeJS) {
JSFunction* function = jsCast<JSFunction*>(callBack);
CachedCall cachedCall(callFrame, function, 1);
- for (auto ptr = set->begin(), end = set->end(); ptr != end && !vm->exception(); ++ptr) {
+ while (iterator->next(callFrame, key) && !vm->exception()) {
cachedCall.setThis(thisValue);
- cachedCall.setArgument(0, ptr.key());
+ cachedCall.setArgument(0, key);
cachedCall.call();
}
+ iterator->finish();
} else {
- for (auto ptr = set->begin(), end = set->end(); ptr != end && !vm->exception(); ++ptr) {
+ while (iterator->next(callFrame, key) && !vm->exception()) {
MarkedArgumentBuffer args;
- args.append(ptr.key());
+ args.append(key);
JSC::call(callFrame, callBack, callType, callData, thisValue, args);
}
+ iterator->finish();
}
return JSValue::encode(jsUndefined());
}
diff --git a/Source/JavaScriptCore/runtime/WeakGCMap.h b/Source/JavaScriptCore/runtime/WeakGCMap.h
index fd1fa6e..16d3d44 100644
--- a/Source/JavaScriptCore/runtime/WeakGCMap.h
+++ b/Source/JavaScriptCore/runtime/WeakGCMap.h
@@ -70,6 +70,17 @@
m_map.clear();
}
+ bool isEmpty() const
+ {
+ const_iterator it = m_map.begin();
+ const_iterator end = m_map.end();
+ while (it != end) {
+ if (it->value)
+ return true;
+ }
+ return false;
+ }
+
iterator find(const KeyType& key)
{
iterator it = m_map.find(key);
@@ -84,6 +95,15 @@
return const_cast<WeakGCMap*>(this)->find(key);
}
+ template<typename Functor>
+ void forEach(Functor functor)
+ {
+ for (auto& pair : m_map) {
+ if (pair.value)
+ functor(pair.key, pair.value.get());
+ }
+ }
+
bool contains(const KeyType& key) const
{
return find(key) != m_map.end();
diff --git a/Source/JavaScriptCore/tests/stress/modify-map-during-iteration.js b/Source/JavaScriptCore/tests/stress/modify-map-during-iteration.js
new file mode 100644
index 0000000..78e438e
--- /dev/null
+++ b/Source/JavaScriptCore/tests/stress/modify-map-during-iteration.js
@@ -0,0 +1,76 @@
+
+function testValue(value, expected) {
+ if (value !== expected)
+ throw new Error("bad value: expected:(" + expected + "),actual:(" + value +").");
+}
+
+function identityPairs(array) {
+ return array.map(function (i) { return [i, i]; });
+}
+
+var map = new Map(identityPairs([0]));
+var counter = 0;
+for (var [elm, _] of map) {
+ testValue(elm, counter);
+ map.set(elm + 1, elm + 1);
+ if (elm > 10000) {
+ map.clear();
+ }
+ ++counter;
+}
+testValue(counter, 10002);
+
+var map = new Map(identityPairs([0, 1, 2, 3]));
+var counter = 0;
+for (var [elm, _] of map) {
+ testValue(elm, counter);
+ map.clear();
+ ++counter;
+}
+testValue(counter, 1);
+
+var map = new Map(identityPairs([0, 1, 2, 3]));
+var exp = [0, 2, 3];
+var counter = 0;
+for (var [elm, _] of map) {
+ testValue(elm, exp[counter]);
+ map.delete(counter + 1);
+ ++counter;
+}
+testValue(counter, 3);
+
+var map = new Map(identityPairs([0, 1, 2, 3]));
+var iter = map.keys();
+var iter2 = map.keys();
+testValue(iter2.next().value, 0);
+
+// Consume all output of iter.
+for (var elm of iter);
+
+testValue(iter.next().done, true);
+testValue(iter.next().value, undefined);
+
+map.clear();
+map.set(1, 1).set(2, 2).set(3, 3);
+
+testValue(iter.next().done, true);
+testValue(iter.next().value, undefined);
+testValue(iter2.next().value, 1);
+testValue(iter2.next().value, 2);
+testValue(iter2.next().value, 3);
+
+var map = new Map();
+map.set(1, 1);
+map.delete(1);
+map.forEach(function (i) {
+ throw new Error("unreeachable.");
+});
+
+var map = new Map();
+var iter = map[Symbol.iterator]();
+map.set(1, 1);
+map.delete(1);
+for (var [elm, _] of map) {
+ throw new Error("unreeachable.");
+}
+
diff --git a/Source/JavaScriptCore/tests/stress/modify-set-during-iteration.js b/Source/JavaScriptCore/tests/stress/modify-set-during-iteration.js
new file mode 100644
index 0000000..da4d20a
--- /dev/null
+++ b/Source/JavaScriptCore/tests/stress/modify-set-during-iteration.js
@@ -0,0 +1,72 @@
+
+function testValue(value, expected) {
+ if (value !== expected)
+ throw new Error("bad value: expected:(" + expected + "),actual:(" + value +").");
+}
+
+var set = new Set([0]);
+var counter = 0;
+for (var elm of set) {
+ testValue(elm, counter);
+ set.add(elm + 1);
+ if (elm > 10000) {
+ set.clear();
+ }
+ ++counter;
+}
+testValue(counter, 10002);
+
+var set = new Set([0, 1, 2, 3]);
+var counter = 0;
+for (var elm of set) {
+ testValue(elm, counter);
+ set.clear();
+ ++counter;
+}
+testValue(counter, 1);
+
+var set = new Set([0, 1, 2, 3]);
+var exp = [0, 2, 3];
+var counter = 0;
+for (var elm of set) {
+ testValue(elm, exp[counter]);
+ set.delete(counter + 1);
+ ++counter;
+}
+testValue(counter, 3);
+
+var set = new Set([0, 1, 2, 3]);
+var iter = set[Symbol.iterator]();
+var iter2 = set[Symbol.iterator]();
+testValue(iter2.next().value, 0);
+
+// Consume all output of iter.
+for (var elm of iter);
+
+testValue(iter.next().done, true);
+testValue(iter.next().value, undefined);
+
+set.clear();
+set.add(1).add(2).add(3);
+
+testValue(iter.next().done, true);
+testValue(iter.next().value, undefined);
+testValue(iter2.next().value, 1);
+testValue(iter2.next().value, 2);
+testValue(iter2.next().value, 3);
+
+var set = new Set();
+set.add(1);
+set.delete(1);
+set.forEach(function (i) {
+ throw new Error("unreeachable.");
+});
+
+var set = new Set();
+var iter = set[Symbol.iterator]();
+set.add(1);
+set.delete(1);
+for (var elm of iter) {
+ throw new Error("unreeachable.");
+}
+
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 2d7bc56..e74e20b 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,17 @@
+2015-03-24 Yusuke Suzuki <utatane.tea@gmail.com>
+
+ REGRESSION (r181458): Heap use-after-free in JSSetIterator destructor
+ https://bugs.webkit.org/show_bug.cgi?id=142696
+
+ Reviewed and tweaked by Geoffrey Garen.
+
+ Use JSSetIterator/JSMapIterator to iterate over JSSet and JSMap.
+
+ * ForwardingHeaders/runtime/JSMapIterator.h: Added.
+ * ForwardingHeaders/runtime/JSSetIterator.h: Added.
+ * bindings/js/SerializedScriptValue.cpp:
+ (WebCore::CloneSerializer::serialize):
+
2015-03-24 Dan Bernstein <mitz@apple.com>
Tried to fix the iOS Simulator build.
diff --git a/Source/WebCore/ForwardingHeaders/runtime/JSMapIterator.h b/Source/WebCore/ForwardingHeaders/runtime/JSMapIterator.h
new file mode 100644
index 0000000..735ccbd
--- /dev/null
+++ b/Source/WebCore/ForwardingHeaders/runtime/JSMapIterator.h
@@ -0,0 +1,4 @@
+#ifndef WebCore_FWD_JSMapIterator_h
+#define WebCore_FWD_JSMapIterator_h
+#include <JavaScriptCore/JSMapIterator.h>
+#endif
diff --git a/Source/WebCore/ForwardingHeaders/runtime/JSSetIterator.h b/Source/WebCore/ForwardingHeaders/runtime/JSSetIterator.h
new file mode 100644
index 0000000..0cb43ee
--- /dev/null
+++ b/Source/WebCore/ForwardingHeaders/runtime/JSSetIterator.h
@@ -0,0 +1,4 @@
+#ifndef WebCore_FWD_JSSetIterator_h
+#define WebCore_FWD_JSSetIterator_h
+#include <JavaScriptCore/JSSetIterator.h>
+#endif
diff --git a/Source/WebCore/bindings/js/SerializedScriptValue.cpp b/Source/WebCore/bindings/js/SerializedScriptValue.cpp
index 757ecd9..ba2603c 100644
--- a/Source/WebCore/bindings/js/SerializedScriptValue.cpp
+++ b/Source/WebCore/bindings/js/SerializedScriptValue.cpp
@@ -61,7 +61,9 @@
#include <runtime/JSCInlines.h>
#include <runtime/JSDataView.h>
#include <runtime/JSMap.h>
+#include <runtime/JSMapIterator.h>
#include <runtime/JSSet.h>
+#include <runtime/JSSetIterator.h>
#include <runtime/JSTypedArrays.h>
#include <runtime/MapData.h>
#include <runtime/MapDataInlines.h>
@@ -1220,10 +1222,8 @@
Vector<uint32_t, 16> lengthStack;
Vector<PropertyNameArray, 16> propertyStack;
Vector<JSObject*, 32> inputObjectStack;
- Vector<JSMap*, 4> mapStack;
- Vector<JSMap::const_iterator, 4> mapIteratorStack;
- Vector<JSSet*, 4> setStack;
- Vector<JSSet::const_iterator, 4> setIteratorStack;
+ Vector<JSMapIterator*, 4> mapIteratorStack;
+ Vector<JSSetIterator*, 4> setIteratorStack;
Vector<JSValue, 4> mapIteratorValueStack;
Vector<WalkerState, 16> stateStack;
WalkerState state = StateUnknown;
@@ -1356,19 +1356,19 @@
JSMap* inMap = jsCast<JSMap*>(inValue);
if (!startMap(inMap))
break;
+ JSMapIterator* iterator = JSMapIterator::create(m_exec->vm(), m_exec->lexicalGlobalObject()->mapIteratorStructure(), inMap, MapIterateKeyValue);
m_gcBuffer.append(inMap);
- mapStack.append(inMap);
- mapIteratorStack.append(inMap->begin());
+ m_gcBuffer.append(iterator);
+ mapIteratorStack.append(iterator);
inputObjectStack.append(inMap);
goto mapDataStartVisitEntry;
}
mapDataStartVisitEntry:
case MapDataStartVisitEntry: {
- JSMap::const_iterator& ptr = mapIteratorStack.last();
- JSMap* map = mapStack.last();
- if (ptr == map->end()) {
+ JSMapIterator* iterator = mapIteratorStack.last();
+ JSValue key, value;
+ if (!iterator->nextKeyValue(key, value)) {
mapIteratorStack.removeLast();
- mapStack.removeLast();
JSObject* object = inputObjectStack.last();
ASSERT(jsDynamicCast<JSMap*>(object));
propertyStack.append(PropertyNameArray(m_exec));
@@ -1377,9 +1377,9 @@
indexStack.append(0);
goto objectStartVisitMember;
}
- inValue = ptr.key();
- m_gcBuffer.append(ptr.value());
- mapIteratorValueStack.append(ptr.value());
+ inValue = key;
+ m_gcBuffer.append(value);
+ mapIteratorValueStack.append(value);
stateStack.append(MapDataEndVisitKey);
goto stateUnknown;
}
@@ -1390,8 +1390,6 @@
goto stateUnknown;
}
case MapDataEndVisitValue: {
- if (mapIteratorStack.last() != mapStack.last()->end())
- ++mapIteratorStack.last();
goto mapDataStartVisitEntry;
}
@@ -1402,19 +1400,19 @@
JSSet* inSet = jsCast<JSSet*>(inValue);
if (!startSet(inSet))
break;
+ JSSetIterator* iterator = JSSetIterator::create(m_exec->vm(), m_exec->lexicalGlobalObject()->setIteratorStructure(), inSet, SetIterateKey);
m_gcBuffer.append(inSet);
- setStack.append(inSet);
- setIteratorStack.append(inSet->begin());
+ m_gcBuffer.append(iterator);
+ setIteratorStack.append(iterator);
inputObjectStack.append(inSet);
goto setDataStartVisitEntry;
}
setDataStartVisitEntry:
case SetDataStartVisitEntry: {
- JSSet::const_iterator& ptr = setIteratorStack.last();
- JSSet* set = setStack.last();
- if (ptr == set->end()) {
+ JSSetIterator* iterator = setIteratorStack.last();
+ JSValue key;
+ if (!iterator->next(m_exec, key)) {
setIteratorStack.removeLast();
- setStack.removeLast();
JSObject* object = inputObjectStack.last();
ASSERT(jsDynamicCast<JSSet*>(object));
propertyStack.append(PropertyNameArray(m_exec));
@@ -1423,13 +1421,11 @@
indexStack.append(0);
goto objectStartVisitMember;
}
- inValue = ptr.key();
+ inValue = key;
stateStack.append(SetDataEndVisitKey);
goto stateUnknown;
}
case SetDataEndVisitKey: {
- if (setIteratorStack.last() != setStack.last()->end())
- ++setIteratorStack.last();
goto setDataStartVisitEntry;
}