Commit e864155a authored by oliver@apple.com's avatar oliver@apple.com

2010-12-20 Oliver Hunt <oliver@apple.com>

        Reviewed by Darin Adler.

        Need to support serialisation of cyclic graphs in the internal structured cloning algorithm
        https://bugs.webkit.org/show_bug.cgi?id=51353

        Update test to cover correct behaviour, and extend to test for actual graph construction.

        * fast/dom/Window/window-postmessage-clone-expected.txt:
        * fast/dom/Window/window-postmessage-clone.html:
2010-12-20  Oliver Hunt  <oliver@apple.com>

        Reviewed by Darin Adler.

        Need to support serialisation of cyclic graphs in the internal structured cloning algorithm
        https://bugs.webkit.org/show_bug.cgi?id=51353

        The Internal Structured Clone algorithm has been changed to allow (and
        correctly clone) cyclic graphs.  This patch updates our implementation
        to provide that functionality.

        I've bumped the serialization version number, and added ObjectReferenceTag
        to represent references to objects that have already been seen.

        * bindings/js/SerializedScriptValue.cpp:
        (WebCore::CloneSerializer::startObjectInternal):
          Now that we have something a bit more complex than cycle checking
          I've replaced the duplicate code in startObject and startArray with
          a shared function that implements that logic to plant an object
          reference
        (WebCore::CloneSerializer::startObject):
        (WebCore::CloneSerializer::startArray):
          Lift out duplicate code
        (WebCore::CloneSerializer::endObject):
          Can't remove objects from the gcbuffer now as they need to remain live
          so we can identify graphs
        (WebCore::CloneSerializer::writeStringIndex):
        (WebCore::CloneSerializer::writeObjectIndex):
        (WebCore::CloneSerializer::writeConstantPoolIndex):
        (WebCore::CloneSerializer::write):
        (WebCore::CloneSerializer::serialize):
        (WebCore::CloneDeserializer::readStringIndex):
        (WebCore::CloneDeserializer::readConstantPoolIndex):
        (WebCore::CloneDeserializer::readTerminal):
        (WebCore::CloneDeserializer::deserialize):

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@74368 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 5e40f1ff
2010-12-20 Oliver Hunt <oliver@apple.com>
Reviewed by Darin Adler.
Need to support serialisation of cyclic graphs in the internal structured cloning algorithm
https://bugs.webkit.org/show_bug.cgi?id=51353
Update test to cover correct behaviour, and extend to test for actual graph construction.
* fast/dom/Window/window-postmessage-clone-expected.txt:
* fast/dom/Window/window-postmessage-clone.html:
2010-12-20 Abhishek Arya <inferno@chromium.org>
Reviewed by James Robinson.
......
Tests that we clone object hierarchies
PASS: 'postMessage(cyclicObject)' threw TypeError: Cannot post cyclic structures.
PASS: 'postMessage(cyclicArray)' threw TypeError: Cannot post cyclic structures.
PASS: 'postMessage(reallyDeepArray)' threw RangeError: Maximum call stack size exceeded.
PASS: 'postMessage(window)' threw TypeError: Type error
PASS: eventData is null of type object
......@@ -25,6 +23,10 @@ PASS: eventData is ,,1 of type object
PASS: eventData is null of type object
PASS: eventData is 2009-02-13T23:31:30.000Z of type object
PASS: eventData is [object Object] of type object
PASS: eventData is === to eventData.self
PASS: eventData is === to eventData[0]
PASS: eventData.graph1 is === to eventData.graph2
PASS: eventData[0] is === to eventData[1]
PASS: eventData is [object Array](default toString threw RangeError: Maximum call stack size exceeded.) of type object
PASS: eventData is [object File] of type object
PASS: eventData is [object FileList] of type object
......
......@@ -80,11 +80,37 @@ function shouldBe(actual, expected)
console.innerHTML += "FAIL: " + actual + " is " + actualValue + " should be " + expectedValue + " of type " + typeof expectedValue + "<br>";
}
function shouldBeIdentical(actual, expected)
{
var actualValue = eval(actual);
var expectedValue = eval(expected);
if (actualValue === expectedValue)
console.innerHTML += "PASS: " + actual + " is === to " + expected + "<br>";
else
console.innerHTML += "FAIL: " + actual + " is not === to " + expected + "<br>";
}
function onmessage(evt) {
eventData = evt.data;
if (evt.data !== evt.data)
console.innerHTML += "MessageEvent.data does not produce the same value on multiple queries.<br>";
shouldBe("eventData", messages.shift());
var message = messages.shift();
switch (message) {
case "cyclicObject":
shouldBeIdentical("eventData", "eventData.self");
break;
case "cyclicArray":
shouldBeIdentical("eventData", "eventData[0]");
break;
case "objectGraph":
shouldBeIdentical("eventData.graph1", "eventData.graph2");
break;
case "arrayGraph":
shouldBeIdentical("eventData[0]", "eventData[1]");
break;
default:
shouldBe("eventData", message);
}
if (safeToString(evt.data) == 'done' && window.layoutTestController)
layoutTestController.notifyDone();
......@@ -141,10 +167,17 @@ tryPostMessage('new Date(1234567890000)');
tryPostMessage('new ConstructorWithPrototype("foo")', false, '({field:"foo"})');
cyclicObject={};
cyclicObject.self = cyclicObject;
tryPostMessage('cyclicObject', true);
tryPostMessage('cyclicObject', false, "cyclicObject");
cyclicArray=[];
cyclicArray[0] = cyclicArray;
tryPostMessage('cyclicArray', true);
tryPostMessage('cyclicArray', false, "cyclicArray");
objectGraph = {};
object = {};
objectGraph.graph1 = object;
objectGraph.graph2 = object;
tryPostMessage('objectGraph', false, "objectGraph");
arrayGraph = [object, object];
tryPostMessage('arrayGraph', false, "arrayGraph");
deepArray=[];
for (var i = 0; i < 10000; i++)
deepArray=[deepArray];
......
2010-12-20 Oliver Hunt <oliver@apple.com>
Reviewed by Darin Adler.
Need to support serialisation of cyclic graphs in the internal structured cloning algorithm
https://bugs.webkit.org/show_bug.cgi?id=51353
The Internal Structured Clone algorithm has been changed to allow (and
correctly clone) cyclic graphs. This patch updates our implementation
to provide that functionality.
I've bumped the serialization version number, and added ObjectReferenceTag
to represent references to objects that have already been seen.
* bindings/js/SerializedScriptValue.cpp:
(WebCore::CloneSerializer::startObjectInternal):
Now that we have something a bit more complex than cycle checking
I've replaced the duplicate code in startObject and startArray with
a shared function that implements that logic to plant an object
reference
(WebCore::CloneSerializer::startObject):
(WebCore::CloneSerializer::startArray):
Lift out duplicate code
(WebCore::CloneSerializer::endObject):
Can't remove objects from the gcbuffer now as they need to remain live
so we can identify graphs
(WebCore::CloneSerializer::writeStringIndex):
(WebCore::CloneSerializer::writeObjectIndex):
(WebCore::CloneSerializer::writeConstantPoolIndex):
(WebCore::CloneSerializer::write):
(WebCore::CloneSerializer::serialize):
(WebCore::CloneDeserializer::readStringIndex):
(WebCore::CloneDeserializer::readConstantPoolIndex):
(WebCore::CloneDeserializer::readTerminal):
(WebCore::CloneDeserializer::deserialize):
2010-12-20 Xan Lopez <xlopez@igalia.com>
Reviewed by Gustavo Noronha.
......
......@@ -86,11 +86,17 @@ enum SerializationTag {
StringTag = 16,
EmptyStringTag = 17,
RegExpTag = 18,
ObjectReferenceTag = 19,
ErrorTag = 255
};
static const unsigned int CurrentVersion = 1;
/* CurrentVersion tracks the serialization version so that persistant stores
* are able to correctly bail out in the case of encountering newer formats.
*
* Initial version was 1.
* Version 2. added the ObjectReferenceTag and support for serialization of cyclic graphs.
*/
static const unsigned int CurrentVersion = 2;
static const unsigned int TerminatorTag = 0xFFFFFFFF;
static const unsigned int StringPoolTag = 0xFFFFFFFE;
......@@ -98,8 +104,9 @@ static const unsigned int StringPoolTag = 0xFFFFFFFE;
* Object serialization is performed according to the following grammar, all tags
* are recorded as a single uint8_t.
*
* IndexType (used for StringData's constant pool) is the sized unsigned integer type
* required to represent the maximum index in the constant pool.
* IndexType (used for the object pool and StringData's constant pool) is the
* minimum sized unsigned integer type required to represent the maximum index
* in the constant pool.
*
* SerializedValue :- <CurrentVersion:uint32_t> Value
* Value :- Array | Object | Terminal
......@@ -124,6 +131,7 @@ static const unsigned int StringPoolTag = 0xFFFFFFFE;
* | FileList
* | ImageData
* | Blob
* | ObjectReferenceTag <opIndex:IndexType>
*
* String :-
* EmptyStringTag
......@@ -237,38 +245,45 @@ private:
return isJSArray(&m_exec->globalData(), object) || object->inherits(&JSArray::info);
}
bool startObject(JSObject* object)
bool startObjectInternal(JSObject* object)
{
// Cycle detection
if (!m_cycleDetector.add(object).second) {
throwError(m_exec, createTypeError(m_exec, "Cannot post cyclic structures."));
// Record object for graph reconstruction
pair<ObjectPool::iterator, bool> iter = m_objectPool.add(object, m_objectPool.size());
// Handle duplicate references
if (!iter.second) {
write(ObjectReferenceTag);
ASSERT(static_cast<int32_t>(iter.first->second) < m_objectPool.size());
writeObjectIndex(iter.first->second);
return false;
}
m_gcBuffer.append(object);
write(ObjectTag);
return true;
}
bool startObject(JSObject* object)
{
if (!startObjectInternal(object))
return false;
write(ObjectTag);
return true;
}
bool startArray(JSArray* array)
{
// Cycle detection
if (!m_cycleDetector.add(array).second) {
throwError(m_exec, createTypeError(m_exec, "Cannot post cyclic structures."));
if (!startObjectInternal(array))
return false;
}
m_gcBuffer.append(array);
unsigned length = array->length();
write(ArrayTag);
write(length);
return true;
}
void endObject(JSObject* object)
void endObject()
{
write(TerminatorTag);
m_cycleDetector.remove(object);
m_gcBuffer.removeLast();
}
JSValue getSparseIndex(JSArray* array, unsigned propertyName, bool& hasIndex)
......@@ -493,9 +508,20 @@ private:
void writeStringIndex(unsigned i)
{
if (m_constantPool.size() <= 0xFF)
writeConstantPoolIndex(m_constantPool, i);
}
void writeObjectIndex(unsigned i)
{
writeConstantPoolIndex(m_objectPool, i);
}
template <class T> void writeConstantPoolIndex(const T& constantPool, unsigned i)
{
ASSERT(static_cast<int32_t>(i) < constantPool.size());
if (constantPool.size() <= 0xFF)
write(static_cast<uint8_t>(i));
else if (m_constantPool.size() <= 0xFFFF)
else if (constantPool.size() <= 0xFFFF)
write(static_cast<uint16_t>(i));
else
write(static_cast<uint32_t>(i));
......@@ -504,7 +530,7 @@ private:
void write(const Identifier& ident)
{
UString str = ident.ustring();
pair<ConstantPool::iterator, bool> iter = m_constantPool.add(str.impl(), m_constantPool.size());
pair<StringConstantPool::iterator, bool> iter = m_constantPool.add(str.impl(), m_constantPool.size());
if (!iter.second) {
write(StringPoolTag);
writeStringIndex(iter.first->second);
......@@ -558,9 +584,10 @@ private:
}
Vector<uint8_t>& m_buffer;
HashSet<JSObject*> m_cycleDetector;
typedef HashMap<RefPtr<StringImpl>, uint32_t, IdentifierRepHash> ConstantPool;
ConstantPool m_constantPool;
typedef HashMap<JSObject*, uint32_t> ObjectPool;
ObjectPool m_objectPool;
typedef HashMap<RefPtr<StringImpl>, uint32_t, IdentifierRepHash> StringConstantPool;
StringConstantPool m_constantPool;
Identifier m_emptyIdentifier;
};
......@@ -588,7 +615,7 @@ bool CloneSerializer::serialize(JSValue in)
JSArray* inArray = asArray(inValue);
unsigned length = inArray->length();
if (!startArray(inArray))
return false;
break;
inputArrayStack.append(inArray);
indexStack.append(0);
lengthStack.append(length);
......@@ -607,7 +634,7 @@ bool CloneSerializer::serialize(JSValue in)
JSArray* array = inputArrayStack.last();
uint32_t index = indexStack.last();
if (index == lengthStack.last()) {
endObject(array);
endObject();
inputArrayStack.removeLast();
indexStack.removeLast();
lengthStack.removeLast();
......@@ -645,7 +672,7 @@ bool CloneSerializer::serialize(JSValue in)
}
JSObject* inObject = asObject(inValue);
if (!startObject(inObject))
return false;
break;
inputObjectStack.append(inObject);
indexStack.append(0);
propertyStack.append(PropertyNameArray(m_exec));
......@@ -666,7 +693,7 @@ bool CloneSerializer::serialize(JSValue in)
uint32_t index = indexStack.last();
PropertyNameArray& properties = propertyStack.last();
if (index == properties.size()) {
endObject(object);
endObject();
inputObjectStack.removeLast();
indexStack.removeLast();
propertyStack.removeLast();
......@@ -904,14 +931,19 @@ private:
bool readStringIndex(uint32_t& i)
{
if (m_constantPool.size() <= 0xFF) {
return readConstantPoolIndex(m_constantPool, i);
}
template <class T> bool readConstantPoolIndex(const T& constantPool, uint32_t& i)
{
if (constantPool.size() <= 0xFF) {
uint8_t i8;
if (!read(i8))
return false;
i = i8;
return true;
}
if (m_constantPool.size() <= 0xFFFF) {
if (constantPool.size() <= 0xFFFF) {
uint16_t i16;
if (!read(i16))
return false;
......@@ -1142,6 +1174,14 @@ private:
RefPtr<RegExp> regExp = RegExp::create(&m_exec->globalData(), pattern->ustring(), flags->ustring());
return new (m_exec) RegExpObject(m_exec->lexicalGlobalObject(), m_globalObject->regExpStructure(), regExp);
}
case ObjectReferenceTag: {
unsigned index = 0;
if (!readConstantPoolIndex(m_gcBuffer, index)) {
fail();
return JSValue();
}
return m_gcBuffer.at(index);
}
default:
m_ptr--; // Push the tag back
return JSValue();
......@@ -1199,7 +1239,6 @@ JSValue CloneDeserializer::deserialize()
}
if (index == TerminatorTag) {
JSArray* outArray = outputArrayStack.last();
m_gcBuffer.removeLast();
outValue = outArray;
outputArrayStack.removeLast();
break;
......@@ -1248,7 +1287,6 @@ JSValue CloneDeserializer::deserialize()
if (!wasTerminator)
goto error;
JSObject* outObject = outputObjectStack.last();
m_gcBuffer.removeLast();
outValue = outObject;
outputObjectStack.removeLast();
break;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment