/* * 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