diff options
Diffstat (limited to 'WebCore/bindings/v8/SerializedScriptValue.cpp')
-rw-r--r-- | WebCore/bindings/v8/SerializedScriptValue.cpp | 699 |
1 files changed, 699 insertions, 0 deletions
diff --git a/WebCore/bindings/v8/SerializedScriptValue.cpp b/WebCore/bindings/v8/SerializedScriptValue.cpp new file mode 100644 index 0000000..bac7f20 --- /dev/null +++ b/WebCore/bindings/v8/SerializedScriptValue.cpp @@ -0,0 +1,699 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "SerializedScriptValue.h" + +#include "SharedBuffer.h" + +#include <v8.h> +#include <wtf/Assertions.h> +#include <wtf/RefCounted.h> +#include <wtf/Vector.h> + +// FIXME: +// - catch V8 exceptions +// - be ready to get empty handles +// - consider crashing in debug mode on deserialization errors + +namespace WebCore { + +namespace { + +typedef UChar BufferValueType; + +// Serialization format is a sequence of (tag, optional data) +// pairs. Tag always takes exactly one byte. +enum SerializationTag { + InvalidTag = '!', + PaddingTag = '\0', + UndefinedTag = '_', + NullTag = '0', + TrueTag = 'T', + FalseTag = 'F', + StringTag = 'S', + Int32Tag = 'I', + NumberTag = 'N', + ObjectTag = '{', + ArrayTag = '[', +}; + +// Helpers to do verbose handle casts. + +template <typename T, typename U> +static v8::Handle<T> handleCast(v8::Handle<U> handle) { return v8::Handle<T>::Cast(handle); } + +template <typename T, typename U> +static v8::Local<T> handleCast(v8::Local<U> handle) { return v8::Local<T>::Cast(handle); } + +static bool shouldCheckForCycles(int depth) +{ + ASSERT(depth >= 0); + // Since we are not required to spot the cycle as soon as it + // happens we can check for cycles only when the current depth + // is a power of two. + return !(depth & (depth - 1)); +} + +static const int maxDepth = 20000; + +// VarInt encoding constants. +static const int varIntShift = 7; +static const int varIntMask = (1 << varIntShift) - 1; + +// ZigZag encoding helps VarInt encoding stay small for negative +// numbers with small absolute values. +class ZigZag { +public: + static uint32_t encode(uint32_t value) + { + if (value & (1U << 31)) + value = ((~value) << 1) + 1; + else + value <<= 1; + return value; + } + + static uint32_t decode(uint32_t value) + { + if (value & 1) + value = ~(value >> 1); + else + value >>= 1; + return value; + } + +private: + ZigZag(); +}; + +// Writer is responsible for serializing primitive types and storing +// information used to reconstruct composite types. +class Writer : Noncopyable { +public: + Writer() : m_position(0) + { + } + + // Write functions for primitive types. + + void writeUndefined() { append(UndefinedTag); } + + void writeNull() { append(NullTag); } + + void writeTrue() { append(TrueTag); } + + void writeFalse() { append(FalseTag); } + + void writeString(const char* data, int length) + { + append(StringTag); + doWriteUint32(static_cast<uint32_t>(length)); + append(data, length); + } + + void writeInt32(int32_t value) + { + append(Int32Tag); + doWriteUint32(ZigZag::encode(static_cast<uint32_t>(value))); + } + + void writeNumber(double number) + { + append(NumberTag); + append(reinterpret_cast<char*>(&number), sizeof(number)); + } + + // Records that a composite object can be constructed by using + // |length| previously stored values. + void endComposite(SerializationTag tag, int32_t length) + { + ASSERT(tag == ObjectTag || tag == ArrayTag); + append(tag); + doWriteUint32(static_cast<uint32_t>(length)); + } + + Vector<BufferValueType>& data() + { + fillHole(); + return m_buffer; + } + +private: + void doWriteUint32(uint32_t value) + { + while (true) { + char b = (value & varIntMask); + value >>= varIntShift; + if (!value) { + append(b); + break; + } + append(b | (1 << varIntShift)); + } + } + + void append(SerializationTag tag) + { + append(static_cast<char>(tag)); + } + + void append(char b) + { + ensureSpace(1); + *charAt(m_position++) = b; + } + + void append(const char* data, int length) + { + ensureSpace(length); + memcpy(charAt(m_position), data, length); + m_position += length; + } + + void ensureSpace(int extra) + { + COMPILE_ASSERT(sizeof(BufferValueType) == 2, BufferValueTypeIsTwoBytes); + m_buffer.grow((m_position + extra + 1) / 2); // "+ 1" to round up. + } + + void fillHole() + { + COMPILE_ASSERT(sizeof(BufferValueType) == 2, BufferValueTypeIsTwoBytes); + // If the writer is at odd position in the buffer, then one of + // the bytes in the last UChar is not initialized. + if (m_position % 2) + *charAt(m_position) = static_cast<char>(PaddingTag); + } + + char* charAt(int position) { return reinterpret_cast<char*>(m_buffer.data()) + position; } + + Vector<BufferValueType> m_buffer; + unsigned m_position; +}; + +class Serializer { +public: + explicit Serializer(Writer& writer) + : m_writer(writer) + , m_state(0) + , m_depth(0) + { + } + + bool serialize(v8::Handle<v8::Value> value) + { + v8::HandleScope scope; + StackCleaner cleaner(&m_state); + if (!doSerialize(value)) + return false; + while (top()) { + int length; + while (!top()->isDone(&length)) { + // Note that doSerialize() can change current top(). + if (!doSerialize(top()->advance())) + return false; + } + m_writer.endComposite(top()->tag(), length); + pop(); + } + return true; + } + +private: + class StateBase : public Noncopyable { + public: + virtual ~StateBase() { } + + // Link to the next state to form a stack. + StateBase* nextState() { return m_next; } + void setNextState(StateBase* next) { m_next = next; } + + // Composite object we're processing in this state. + v8::Handle<v8::Value> composite() { return m_composite; } + + // Serialization tag for the current composite. + virtual SerializationTag tag() const = 0; + + // Returns whether iteration over subobjects of the current + // composite object is done. If yes, |*length| is set to the + // number of subobjects. + virtual bool isDone(int* length) = 0; + + // Advances to the next subobject. + // Requires: !this->isDone(). + virtual v8::Local<v8::Value> advance() = 0; + + protected: + StateBase(v8::Handle<v8::Value> composite) + : m_next(0) + , m_composite(composite) + { + } + + private: + StateBase* m_next; + v8::Handle<v8::Value> m_composite; + }; + + template <typename T, SerializationTag compositeTag> + class State : public StateBase { + public: + v8::Handle<T> composite() { return handleCast<T>(StateBase::composite()); } + + virtual SerializationTag tag() const { return compositeTag; } + + protected: + explicit State(v8::Handle<T> composite) : StateBase(composite) + { + } + }; + + // Helper to clean up the state stack in case of errors. + class StackCleaner : Noncopyable { + public: + explicit StackCleaner(StateBase** stack) : m_stack(stack) + { + } + + ~StackCleaner() + { + StateBase* state = *m_stack; + while (state) { + StateBase* tmp = state->nextState(); + delete state; + state = tmp; + } + *m_stack = 0; + } + + private: + StateBase** m_stack; + }; + + class ArrayState : public State<v8::Array, ArrayTag> { + public: + ArrayState(v8::Handle<v8::Array> array) + : State<v8::Array, ArrayTag>(array) + , m_index(0) + { + } + + virtual bool isDone(int* length) + { + *length = composite()->Length(); + return static_cast<int>(m_index) >= *length; + } + + virtual v8::Local<v8::Value> advance() + { + ASSERT(m_index < composite()->Length()); + v8::HandleScope scope; + return scope.Close(composite()->Get(v8::Integer::New(m_index++))); + } + + private: + unsigned m_index; + }; + + class ObjectState : public State<v8::Object, ObjectTag> { + public: + ObjectState(v8::Handle<v8::Object> object) + : State<v8::Object, ObjectTag>(object) + , m_propertyNames(object->GetPropertyNames()) + , m_index(-1) + , m_length(0) + { + nextProperty(); + } + + virtual bool isDone(int* length) + { + *length = m_length; + return m_index >= 2 * m_propertyNames->Length(); + } + + virtual v8::Local<v8::Value> advance() + { + ASSERT(m_index < 2 * m_propertyNames->Length()); + if (!(m_index % 2)) { + ++m_index; + return m_propertyName; + } + v8::Local<v8::Value> result = composite()->Get(m_propertyName); + nextProperty(); + return result; + } + + private: + void nextProperty() + { + v8::HandleScope scope; + ++m_index; + ASSERT(!(m_index % 2)); + for (; m_index < 2 * m_propertyNames->Length(); m_index += 2) { + v8::Local<v8::Value> propertyName = m_propertyNames->Get(v8::Integer::New(m_index / 2)); + if ((propertyName->IsString() && composite()->HasRealNamedProperty(handleCast<v8::String>(propertyName))) + || (propertyName->IsInt32() && composite()->HasRealIndexedProperty(propertyName->Uint32Value()))) { + m_propertyName = scope.Close(propertyName); + m_length += 2; + return; + } + } + } + + v8::Local<v8::Array> m_propertyNames; + v8::Local<v8::Value> m_propertyName; + unsigned m_index; + unsigned m_length; + }; + + bool doSerialize(v8::Handle<v8::Value> value) + { + if (value->IsUndefined()) + m_writer.writeUndefined(); + else if (value->IsNull()) + m_writer.writeNull(); + else if (value->IsTrue()) + m_writer.writeTrue(); + else if (value->IsFalse()) + m_writer.writeFalse(); + else if (value->IsInt32()) + m_writer.writeInt32(value->Int32Value()); + else if (value->IsNumber()) + m_writer.writeNumber(handleCast<v8::Number>(value)->Value()); + else if (value->IsString()) { + v8::String::Utf8Value stringValue(value); + m_writer.writeString(*stringValue, stringValue.length()); + } else if (value->IsArray()) { + if (!checkComposite(value)) + return false; + push(new ArrayState(handleCast<v8::Array>(value))); + } else if (value->IsObject()) { + if (!checkComposite(value)) + return false; + push(new ObjectState(handleCast<v8::Object>(value))); + // FIXME: + // - check not a wrapper + // - support File, ImageData, etc. + } + return true; + } + + void push(StateBase* state) + { + state->setNextState(m_state); + m_state = state; + ++m_depth; + } + + StateBase* top() { return m_state; } + + void pop() + { + if (!m_state) + return; + StateBase* top = m_state; + m_state = top->nextState(); + delete top; + --m_depth; + } + + bool checkComposite(v8::Handle<v8::Value> composite) + { + if (m_depth > maxDepth) + return false; + if (!shouldCheckForCycles(m_depth)) + return true; + for (StateBase* state = top(); state; state = state->nextState()) { + if (state->composite() == composite) + return false; + } + return true; + } + + Writer& m_writer; + StateBase* m_state; + int m_depth; +}; + +// Reader is responsible for deserializing primitive types and +// restoring information about saved objects of composite types. +class Reader { +public: + Reader(const char* buffer, int length) + : m_buffer(buffer) + , m_length(length) + , m_position(0) + { + ASSERT(length >= 0); + } + + bool isEof() const { return m_position >= m_length; } + + bool read(SerializationTag* tag, v8::Handle<v8::Value>* value, int* length) + { + uint32_t rawLength; + if (!readTag(tag)) + return false; + switch (*tag) { + case InvalidTag: + return false; + case PaddingTag: + break; + case UndefinedTag: + *value = v8::Undefined(); + break; + case NullTag: + *value = v8::Null(); + break; + case TrueTag: + *value = v8::True(); + break; + case FalseTag: + *value = v8::False(); + break; + case StringTag: + if (!readString(value)) + return false; + break; + case Int32Tag: + if (!readInt32(value)) + return false; + break; + case NumberTag: + if (!readNumber(value)) + return false; + break; + case ObjectTag: + case ArrayTag: + if (!doReadUint32(&rawLength)) + return false; + *length = rawLength; + break; + } + return true; + } + +private: + bool readTag(SerializationTag* tag) + { + if (m_position >= m_length) + return false; + *tag = static_cast<SerializationTag>(m_buffer[m_position++]); + return true; + } + + bool readString(v8::Handle<v8::Value>* value) + { + uint32_t length; + if (!doReadUint32(&length)) + return false; + if (m_position + length > m_length) + return false; + *value = v8::String::New(m_buffer + m_position, length); + m_position += length; + return true; + } + + bool readInt32(v8::Handle<v8::Value>* value) + { + uint32_t rawValue; + if (!doReadUint32(&rawValue)) + return false; + *value = v8::Integer::New(static_cast<int32_t>(ZigZag::decode(rawValue))); + return true; + } + + bool readNumber(v8::Handle<v8::Value>* value) + { + if (m_position + sizeof(double) > m_length) + return false; + double number; + char* numberAsByteArray = reinterpret_cast<char*>(&number); + for (unsigned i = 0; i < sizeof(double); ++i) + numberAsByteArray[i] = m_buffer[m_position++]; + *value = v8::Number::New(number); + return true; + } + + bool doReadUint32(uint32_t* value) + { + *value = 0; + char currentByte; + int shift = 0; + do { + if (m_position >= m_length) + return false; + currentByte = m_buffer[m_position++]; + *value |= ((currentByte & varIntMask) << shift); + shift += varIntShift; + } while (currentByte & (1 << varIntShift)); + return true; + } + + const char* m_buffer; + const unsigned m_length; + unsigned m_position; +}; + +class Deserializer { +public: + explicit Deserializer(Reader& reader) : m_reader(reader) + { + } + + v8::Local<v8::Value> deserialize() + { + v8::HandleScope scope; + while (!m_reader.isEof()) { + if (!doDeserialize()) + return v8::Local<v8::Value>(); + } + if (stackDepth() != 1) + return v8::Local<v8::Value>(); + return scope.Close(element(0)); + } + +private: + bool doDeserialize() + { + SerializationTag tag; + v8::Local<v8::Value> value; + int length = 0; + if (!m_reader.read(&tag, &value, &length)) + return false; + if (!value.IsEmpty()) { + push(value); + } else if (tag == ObjectTag) { + if (length > stackDepth()) + return false; + v8::Local<v8::Object> object = v8::Object::New(); + for (int i = stackDepth() - length; i < stackDepth(); i += 2) { + v8::Local<v8::Value> propertyName = element(i); + v8::Local<v8::Value> propertyValue = element(i + 1); + object->Set(propertyName, propertyValue); + } + pop(length); + push(object); + } else if (tag == ArrayTag) { + if (length > stackDepth()) + return false; + v8::Local<v8::Array> array = v8::Array::New(length); + const int depth = stackDepth() - length; + { + v8::HandleScope scope; + for (int i = 0; i < length; ++i) + array->Set(v8::Integer::New(i), element(depth + i)); + } + pop(length); + push(array); + } else if (tag != PaddingTag) + return false; + return true; + } + + void push(v8::Local<v8::Value> value) { m_stack.append(value); } + + void pop(unsigned length) + { + ASSERT(length <= m_stack.size()); + m_stack.shrink(m_stack.size() - length); + } + + int stackDepth() const { return m_stack.size(); } + + v8::Local<v8::Value> element(unsigned index) + { + ASSERT(index < m_stack.size()); + return m_stack[index]; + } + + Reader& m_reader; + Vector<v8::Local<v8::Value> > m_stack; +}; + +} // namespace + +SerializedScriptValue::SerializedScriptValue(v8::Handle<v8::Value> value) +{ + Writer writer; + Serializer serializer(writer); + if (!serializer.serialize(value)) { + // FIXME: throw exception + return; + } + m_data = StringImpl::adopt(writer.data()); +} + +SerializedScriptValue::SerializedScriptValue(String data, StringDataMode mode) +{ + if (mode == WireData) + m_data = data; + else { + ASSERT(mode == StringValue); + RefPtr<SharedBuffer> buffer = utf8Buffer(data); + Writer writer; + writer.writeString(buffer->data(), buffer->size()); + m_data = StringImpl::adopt(writer.data()); + } +} + +v8::Local<v8::Value> SerializedScriptValue::deserialize() +{ + if (!m_data.impl()) + return v8::Local<v8::Value>(); + COMPILE_ASSERT(sizeof(BufferValueType) == 2, BufferValueTypeIsTwoBytes); + Reader reader(reinterpret_cast<const char*>(m_data.impl()->characters()), 2 * m_data.length()); + Deserializer deserializer(reader); + return deserializer.deserialize(); +} + +} // namespace WebCore |