core/AvmSerializer.cpp
author Dan Schaffer <Dan.Schaffer@adobe.com>
Tue, 29 Jan 2013 13:37:51 -0800
changeset 7587 5571cf86fc68
parent 7544 9b6b37063cbe
permissions -rw-r--r--
no bug: assorted bug fixes concurrency, telemetry (p=jasowill,p=dtomack,p=gcomnino) integrate CL# 1155278 CL@1159600

/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */
/* vi: set ts=4 sw=4 expandtab: (add to ~/.vimrc: set modeline modelines=5) */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "avmplus.h"

namespace avmplus
{

#define BUILTIN_TRAITS_(class_id) toplevel->core()->builtinPool->getClassTraits(avmplus::NativeID::abcclass_##class_id)->itraits

    static const char kTransient[] = "Transient";
    static const char kWriteExternal[] = "writeExternal";
    static const char kReadExternal[] = "readExternal";

//---------------------------------------------------------------------
// class ObjectOutput is the base class for classes ClassicObjectOutput
// and AvmPlusObjectOutput
//

    ObjectOutput::ObjectOutput(Toplevel *tl)
        : m_listClassInfo(tl->core()->GetGC(), 64)
        , m_htString(HeapHashtable::create(tl->core()->GetGC()))
        , m_htTraits(HeapHashtable::create(tl->core()->GetGC()))
        , m_htObject(HeapHashtable::create(tl->core()->GetGC()))
    {
    }

    ObjectOutput::~ObjectOutput()
    {
        HeapHashtable* temp;
        
        temp = m_htString;
        *(HeapHashtable **)&m_htString = NULL;
        delete temp;

        temp = m_htTraits;
        *(HeapHashtable **)&m_htTraits = NULL;
        delete temp;

        temp = m_htObject;
        *(HeapHashtable **)&m_htObject = NULL;
        delete temp;
    }

    void ObjectOutput::TableAdd(HeapHashtable *ht, Atom atom)
    {
        // this may realloc *ppHashtable
        // just store the raw int value.  we encapsulate access to these tables so it's
        // okay that the keys are atoms and the values are raw ints.
        ht->add(atom, core()->intToAtom(ht->getSize()));
    }

    int32_t ObjectOutput::TableFind(HeapHashtable *ht, Atom atom)
    {
        if(!ht->contains(atom))
            return -1;

        return core()->integer(ht->get(atom));
    }
// end class ObjectOutput
//---------------------------------------------------------------------

//---------------------------------------------------------------------
// class ObjectInput is the base class for classes ClassicObjectInput
// and AvmPlusObjectInput
//

    ObjectInput::ObjectInput(Toplevel *tl)
        : m_listString(tl->core()->GetGC(), 64),
          m_listObject(tl->core()->GetGC(), 64),
          m_listClassInfo(tl->core()->GetGC(), 64)
    {
    }


    void ObjectInput::SetObjectProperty(Atom objectAtom, Stringp name, Atom value)
    {
        TRY(core(), kCatchAction_Ignore)
        {
            Toplevel* toplevel = this->toplevel();
            toplevel->setpropname(objectAtom, name, value);
        }
        CATCH(Exception *exception)
        {
            // http://flashqa.macromedia.com/bugapp/detail.asp?ID=144290
            //
            // Flex has requested read suppresses exceptions thrown while adding members
            // to the object being created (for instance, if the class is sealed and
            // does not contain member name).

            // output the error to the console so it's not entirely lost.
            core()->console << core()->string(exception->atom) << "\n";
        }
        END_CATCH
        END_TRY
    }


    String *ObjectInput::StringListFind(uint32_t i)
    {
        if (i >= m_listString.length())
        {
            ThrowRangeError();
        }

        return m_listString.get(i);
    }

    GCRef<ScriptObject> ObjectInput::ObjectListFind(uint32_t i)
    {
        if (i >= m_listObject.length())
        {
            ThrowRangeError();
        }

        return GCREF_CASTFROMVOID(ScriptObject, m_listObject.get(i));
    }

    ClassInfo *ObjectInput::ClassInfoListFind(uint32_t i)
    {
        if (i >= m_listClassInfo.length())
        {
            ThrowRangeError();
        }

        return m_listClassInfo.get(i);
    }

// end class ObjectInput
//---------------------------------------------------------------------

//---------------------------------------------------------------------
// BEGIN class AvmPlusObjectOutput
//

    void AvmPlusObjectOutput::WriteUint29(uint32_t ref)
    {
        // Represent smaller integers with fewer bytes using the most
        // significant bit of each byte. The worst case uses 32-bits
        // to represent a 29-bit number, which is what we would have
        // done with no compression.

        // 0x00000000 - 0x0000007F : 0xxxxxxx
        // 0x00000080 - 0x00003FFF : 1xxxxxxx 0xxxxxxx
        // 0x00004000 - 0x001FFFFF : 1xxxxxxx 1xxxxxxx 0xxxxxxx
        // 0x00200000 - 0x3FFFFFFF : 1xxxxxxx 1xxxxxxx 1xxxxxxx xxxxxxxx
        // 0x40000000 - 0xFFFFFFFF : throw range exception

        if (ref < 0x80) {
            // 0x00000000 - 0x0000007F : 0xxxxxxx
            WriteU8((uint8_t)ref);
        } else if (ref < 0x4000) {
            // 0x00000080 - 0x00003FFF : 1xxxxxxx 0xxxxxxx
            WriteU8((uint8_t)(((ref >> 7) & 0x7F) | 0x80));
            WriteU8((uint8_t)(ref & 0x7F));
        } else if (ref < 0x200000) {
            // 0x00004000 - 0x001FFFFF : 1xxxxxxx 1xxxxxxx 0xxxxxxx
            WriteU8((uint8_t)(((ref >> 14) & 0x7F) | 0x80));
            WriteU8((uint8_t)(((ref >>  7) & 0x7F) | 0x80));
            WriteU8((uint8_t)(ref & 0x7F));
        } else if (ref < 0x40000000) {
            // 0x00200000 - 0x3FFFFFFF : 1xxxxxxx 1xxxxxxx 1xxxxxxx xxxxxxxx
            WriteU8((uint8_t)(((ref >> 22) & 0x7F) | 0x80));
            WriteU8((uint8_t)(((ref >> 15) & 0x7F) | 0x80));
            WriteU8((uint8_t)(((ref >>  8) & 0x7F) | 0x80));
            WriteU8((uint8_t)(ref & 0xFF));
        } else {
            // 0x40000000 - 0xFFFFFFFF : throw range exception
            ThrowRangeError();
        }
    }

    void AvmPlusObjectOutput::WriteString(String *str) {

        if (str->length() == 0)
        {
            // don't create a reference for the empty string,
            // as it's represented by the one byte value 1
            // len = 0, ((len << 1) | 1).
            WriteReference(1);
            return;
        }

        // intern so (str1 == str2) is true if the strings are the same.
        str = core()->internString(str);

        int32_t ref = StringTableFind(str);

        if (ref >= 0)
        {
            // this is a duplicate of a previous String, so write a reference.
            WriteReference(ref << 1);
        }
        else
        {
            StringTableAdd(str);

            StUTF8String utf8(str);
            uint32_t len = utf8.length();

            WriteReference((len << 1) | 1);
            Write(utf8.c_str(), len);
        }
    }


    void AvmPlusObjectOutput::WriteAtom(Atom value)
    {
        if (handleAdditionalAtom(value))
            return; //Handled by a sub-class implementation. Nothing to be done here
            
        GCRef<Toplevel> toplevel = this->toplevel();
        GCRef<AvmCore> core = toplevel->core();
        GCRef<builtinClassManifest> builtinClasses = toplevel->builtinClasses();

        if (AvmCore::isUndefined(value))
        {
            WriteType(kUndefinedAtomType);
        }
        else if (AvmCore::isNull(value))
        {
            WriteType(kNullAtomType);
        }
        else if (AvmCore::isBoolean(value))
        {
            if (value == trueAtom)
            {
                WriteType(kTrueAtomType);
            }
            else
            {
                WriteType(kFalseAtomType);
            }

        }
        else if (atomIsIntptr(value))
        {
            intptr_t const val = atomGetIntptr(value);
            // Does our integer fit into 29 bits?
            if (atomCanBeInt32(value) && ((int32_t(val)<<3)>>3) == val)
            {
                // 32-bit: AVM+ integers are int29, so use reference
                // 64-bit: Our check above protects large integers
                WriteType(kIntegerAtomType);
                WriteUint29(val & 0x1fffffff);
            }
            else
            {
                WriteType(kDoubleAtomType);
                WriteDouble((double)val);
            }
        }
        else if (AvmCore::isDouble(value))
        {
            WriteType(kDoubleAtomType);
            WriteDouble(AvmCore::number(value));
        } 
#ifdef VMCFG_FLOAT
        else if(AvmCore::isFloat(value))
        {
            WriteType(kFloatAtomType);
            WriteFloat(AvmCore::atomToFloat(value));
        }
        else if(AvmCore::isFloat4(value))
        {
            float4_t floatVal = AvmCore::atomToFloat4(value);
            WriteType(kFloat4AtomType);
            WriteFloat4(floatVal);
        }
#endif// VMCFG_FLOAT
        else if (AvmCore::isString(value)) 
        {
            WriteType(kStringAtomType);
            WriteString(core->string(value));
        }
        else if (core->isFunction(value))
        {
            // don't serialize functions
            WriteType(kUndefinedAtomType);
        }
        else if (!AvmCore::isObject(value))
        {
            // We've enumerated everything we know but object,
            // so if it isn't an object, it's something we cannot serialize.
            ThrowArgumentError();
        }
        else
        {
            GCRef<ScriptObject> obj = AvmCore::atomToScriptObject(value);

            // cn: Note we ignore XMLList type here, which is actually different from the XML type.
            //     AMF is a published format, however, so we can't add a new datatype to correctly
            //     handle XMLList.  We'll write a generic Object type in that case (see default below),
            //     workaround is to wrap your XMLList value in an XML wrapper.

            if (AvmCore::istype(value, core->traits.xml_itraits))
            {
                WriteType(kAvmPlusXmlAtomType);
                WriteXML(obj,true);
            }
            else if (AvmCore::istype(value, core->traits.date_itraits))
            {
                WriteType(kDateAtomType);
                WriteDate(obj.staticCast<DateObject>());
            }
            else if (AvmCore::istype(value, core->traits.array_itraits))
            {
                WriteType(kArrayAtomType);
                WriteArray(obj.staticCast<ArrayObject>());
            }
            else if (builtinClasses->get_ByteArrayClass()->isType(value))
            {
                WriteType(kByteArrayAtomType);
                WriteByteArray(obj.staticCast<ByteArrayObject>());
            }
            else if (builtinClasses->get_DictionaryClass()->isType(value))
            {
                WriteDictionary(obj.staticCast<DictionaryObject>());
            }
            else if (AvmCore::istype(value, VECTORINT_TYPE) ||
                        AvmCore::istype(value, VECTORUINT_TYPE) ||
                        AvmCore::istype(value, VECTORDOUBLE_TYPE) ||
#ifdef VMCFG_FLOAT
                        AvmCore::istype(value, VECTORFLOAT_TYPE) ||
                        AvmCore::istype(value, VECTORFLOAT4_TYPE) ||
#endif
                        AvmCore::istype(value, VECTOROBJ_TYPE)) 
            {
                // We assume vectors can only be created from within SWF10 files.
                WriteTypedVector(value);
            }
            else
            {
                WriteType(kObjectAtomType);
                WriteScriptObject(obj);
            }
        }
    }

    void AvmPlusObjectOutput::WriteXML(GCRef<ScriptObject> obj, bool isAS3XML)
    {
        int32_t ref = ObjectTableFind(obj);

        if (ref >= 0)
        {
            // output already contains object. Save a reference to previous instance.
            WriteReference(ref << 1);
            return;
        }

        ObjectTableAdd(obj);

        String* str = NULL;
        // cn:  XML's toString() will skip the tags if the content is simple (i.e. <f>hi</f>.toString() == "hi")
        //        use toXMLString instead.
        if (isAS3XML)
        {
            XMLObject* x = obj.staticCast<XMLObject>();
            str = x->toXMLString();
        }
        else
        {
            str = obj->toString();
        }

        StUTF8String utf8(str);
        uint32_t len = utf8.length();

        // Store length in the surplus bits of the invalid reference
        WriteReference((len << 1) | 1);
        Write(utf8.c_str(), len);
    }

    void AvmPlusObjectOutput::WriteDate(DateObject *obj)
    {
        int32_t ref = ObjectTableFind(obj);

        if (ref >= 0)
        {
            // output already contains object. Save a reference to previous instance.
            WriteReference(ref << 1);
            return;
        }
        
        ObjectTableAdd(obj);

        // write an invalid reference, followed by the date.
        // As the date is only a double, it's hard to re-use the surplus bits of the reference.
        WriteReference(1);
        WriteDouble(obj->date.getTime());
    }

    void AvmPlusObjectOutput::WriteByteArray(GCRef<ByteArrayObject> obj)
    {
        int32_t ref = ObjectTableFind(obj);

        if (ref >= 0)
        {
            // output already contains object. Save a reference to previous instance.
            WriteReference(ref << 1);
            return;
        }
        
        ObjectTableAdd(obj);

        //
        // obj might be the ByteArray we are writing to, such as
        //  o.ba = ba;
        //  ba.writeObject(o);
        // so play it safe and duplicate the ByteArray before copying.
        //
        // ByteArray ba(obj->GetByteArray());
        
        // actually, it's been legal for a ByteArray to write to itself for quite
        // a while, so duplicating this data is unnecessary
        ByteArray& ba = obj->GetByteArray();

        int len = ba.GetLength();
        const uint8_t* buf = ba.GetReadableBuffer();

        // write an invalid reference, but re-use the surplus bits to store the length
        WriteReference((len << 1) | 1);
        Write(buf, len);
    }

    void AvmPlusObjectOutput::WriteArray(GCRef<ArrayObject> obj)
    {
        int32_t ref = ObjectTableFind(obj);

        if (ref >= 0)
        {
            // output already contains object. Save a reference to previous instance.
            WriteReference(ref << 1);
            return;
        }
        
        ObjectTableAdd(obj);

        // Arrays could be written just like Objects, as an undefined terminated
        // list of {name, value} pairs, but we'd like to avoid writing the name's
        // if they are all integers. So, we calculate the smallest range that has no
        // holes, and emit that as a list, with any extra properties getting emitted as a map.
        // (Array used to have a "getDenseLength" call that would we'd use, but that relied
        // on the underlying representation, which has changed in the name of efficiency;
        // therefore we calculate it manually here. Note that this could cause some arrays
        // to be written in slightly different form (ie with more name/value pairs), but that's
        // OK, just suboptimal.)
        uint32_t denseLen = obj->getLength();
        for (uint32_t i = 0, n = denseLen; i < n; ++i)
        {
            if (!obj->hasUintProperty(i))
            {
                denseLen = i;
                break;
            }
        }

        uint32_t len;
        int index;
        uint32_t i;

        // This is a bit of a pest, but we don't write out functions,
        // and we don't want any holes in the dense portion. AVM-
        // wrote an undefined in this case, but we don't want read to
        // create an undefined element, and so we scan this dense portion
        // searching for functions, and contract the length to exclude them.

        for (i = 0, index = 0, len = denseLen; (i < len); i++)
        {
            index = obj->nextNameIndex(index);
            Atom value = obj->nextValue(index);

            if (AvmCore::istype(value, core()->traits.function_itraits))
            {
                len = i;
                break;
            }
        }

        // Write an invalid reference. Repurpose the unused bits to store the number of dense elements.
        WriteReference((len << 1) | 1);

        // When the entire Array is dense, the server will create a List.
        // When the Array has holes, or contains elements indexed with non-numeric values,
        // the server will create a Map.
        //
        // Therefore, it's easier for the server if we write out the non-dense portion first.
        while ((index = obj->nextNameIndex(index)) != 0)
        {
            Atom name = obj->nextName(index);
            Atom value = obj->nextValue(index);

            if (AvmCore::istype(value, core()->traits.function_itraits))
            {
                continue;
            }

            WriteString(core()->string(name));
            WriteAtom(value);
        }

        // Terminate the non-dense portion with an empty string (which is invalid as a property name)
        WriteString(core()->kEmptyString);

        // Write out the dense portion of the array
        for (i = 0, index = 0; i < len; i++)
        {
            index = obj->nextNameIndex(index);
            WriteAtom(obj->nextValue(index));
        }
    }

    void AvmPlusObjectOutput::WriteDictionary(GCRef<DictionaryObject> obj)
    {
        int32_t ref = ObjectTableFind(obj);

        WriteType(kDictionaryObject);

        if (ref >= 0)
        {
            // output already contains object. Save a reference to previous instance.
            WriteReference(ref << 1);
            return;
        }
        
        ObjectTableAdd(obj);

        InlineHashtable *h = obj->getTable();

        WriteReference((h->getSize()<<1)|1);
        
        WriteBoolean(obj->isUsingWeakKeys());
        
        for (uint32_t c = h->next(0); c ; c = h->next(c))
        {
            Atom key = h->keyAt(c);
            // The dictionary class converts keys to integers if possible
            // so convert it back to a string if neccessary.
            if (atomIsIntptr(key))
            {
                key = MathUtils::convertIntegerToStringRadix(core(), atomGetIntptr(key), 10, MathUtils::kTreatAsSigned)->atom();
            }
            WriteAtom(key);
            WriteAtom(h->valueAt(c));
        }
    }

    void AvmPlusObjectOutput::WriteTypedVector(Atom atom)
    {
        Toplevel* toplevel = this->toplevel();
        AvmCore* core = toplevel->core();

        GCRef<ScriptObject> obj =  core->atomToScriptObject(atom);
        int32_t ref = ObjectTableFind(obj);

        uint8_t vec_type = 0;
        // Write out the type - have to do this before we write out the reference
        if (AvmCore::istype(atom, VECTORINT_TYPE))
        {
            vec_type = kTypedVectorInt;
        }
        else if (AvmCore::istype(atom, VECTORUINT_TYPE))
        {
            vec_type = kTypedVectorUint;
        }
        else if (AvmCore::istype(atom, VECTORDOUBLE_TYPE))
        {
            vec_type = kTypedVectorDouble;
        } 
#ifdef VMCFG_FLOAT
        else if(AvmCore::istype(atom, VECTORFLOAT_TYPE))
        {
            vec_type = kTypedVectorFloat;
        }
        else if(AvmCore::istype(atom, VECTORFLOAT4_TYPE))
        {
            vec_type = kTypedVectorFloat4;
        }
#endif// VMCFG_FLOAT
        else 
        {
            vec_type = kTypedVectorObject;
        }
        
        WriteType(vec_type);

        if (ref >= 0)
        {
            // output already contains object. Save a reference to previous instance.
            WriteReference(ref << 1);
            return;
        }
        
        ObjectTableAdd(obj);

        if (vec_type == kTypedVectorInt)
        {
            IntVectorObject *v = obj.staticCast<IntVectorObject>();
            WriteReference((v->get_length()<<1)|1);
            WriteBoolean(v->get_fixed());

            IntVectorAccessor va(v);
            const int32_t* data = va.addr();
            uint32_t len = va.length();
            for (uint32_t c=0; c<len; c++)
            {
                WriteU32(uint32_t(data[c]));
            }
            
        }
        else if (vec_type == kTypedVectorUint)
        {
            UIntVectorObject *v = obj.staticCast<UIntVectorObject>();
            WriteReference((v->get_length()<<1)|1);
            WriteBoolean(v->get_fixed());

            UIntVectorAccessor va(v);
            const uint32_t* data = va.addr();
            uint32_t len = va.length();
            for (uint32_t c=0; c<len; c++)
            {
                WriteU32(data[c]);
            }
            
        }
        else if (vec_type == kTypedVectorDouble)
        {
            DoubleVectorObject *v = obj.staticCast<DoubleVectorObject>();
            WriteReference((v->get_length()<<1)|1);
            WriteBoolean(v->get_fixed());

            DoubleVectorAccessor va(v);
            const double* data = va.addr();
            uint32_t len = va.length();
            for (uint32_t c=0; c<len; c++)
            {
                WriteDouble(data[c]);
            }
        } 
#ifdef VMCFG_FLOAT
        else if (vec_type == kTypedVectorFloat) 
        {
            FloatVectorObject *v = obj.staticCast<FloatVectorObject>();
            WriteReference((v->get_length()<<1)|1);
            WriteBoolean(v->get_fixed());

            FloatVectorAccessor va(v);
            const float* data = va.addr();
            uint32_t len = va.length();
            for (uint32_t c=0; c<len; c++) 
            {
                WriteFloat(data[c]);
            }
        } 
        else if (vec_type == kTypedVectorFloat4) 
        {
            Float4VectorObject *v = obj.staticCast<Float4VectorObject>();
            WriteReference((v->get_length()<<1)|1);
            WriteBoolean(v->get_fixed());

            Float4VectorAccessor va(v);
            const float4_t* data = va.addr();
            uint32_t len = va.length();
            for (uint32_t c=0; c<len; c++) 
            {
                WriteFloat4(data[c]);
            }
        } 
#endif // VMCFG_FLOAT
        else 
        {
            ObjectVectorObject *v = obj.staticCast<ObjectVectorObject>();
            WriteReference((v->get_length()<<1)|1);
            WriteBoolean(v->get_fixed());

            // Write prototype
            Traits* traits = v->getTypeTraits();
            Stringp classname;
            if (traits)
            {
                classname = toplevel->getAliasFromTraits(traits);
            }
            else
            {
                classname = core->kAsterisk;
            }
            WriteString(classname);

            // Write contents
            uint32_t len = v->get_length();
            for (uint32_t c=0; c<len; c++)
            {
                WriteAtom(v->getUintProperty(c));
            }
        }
    }

    void AvmPlusObjectOutput::WriteScriptObject(GCRef<ScriptObject> obj)
    {
        int32_t ref = ObjectTableFind(obj);

        if (ref >= 0)
        {
            // output already contains object. Save a reference to previous instance.
            WriteReference(ref << 1);
            return;
        }
        
        ObjectTableAdd(obj);

        Toplevel* toplevel = this->toplevel();
        AvmCore* core = toplevel->core();

        //
        // Start an object with its class information
        //
        Traits *traits = obj->traits();
        ClassInfo *info;
        int i, count;
        bool dynamic;
        bool externalizable;

        ref = TraitsTableFind(traits);
        if (ref >= 0)
        {
            // output already contains class info. Save a reference to previous instance.
            WriteReference((ref << 2) | 1);
            info = ClassInfoListFind(ref);
            count = info->SealedPropCount();
            dynamic = info->dynamic();
            externalizable = info->externalizable();
        }
        else
        {
            // Create class info
            info = new (core->GetGC()) ClassInfo(toplevel, traits);

            // Add class info to relate tables
            ClassInfoListAdd(info);
            TraitsTableAdd(traits);
            AvmAssert(ClassInfoListFind(TraitsTableFind(traits)) == info);

            // write out class info, using the surplas bits of the reference to
            // store dynamic and the number of sealed properties
            count = info->SealedPropCount();
            dynamic = info->dynamic();
            externalizable = info->externalizable();

            WriteReference(3 | (externalizable ? 4 : 0) | (dynamic ? 8 : 0) | (count << 4));
            info->Write(this);
        }

        if (externalizable)
        {
            // ClassInfo's constructor has already verified this, so we don't need to repeat the error handling here
            AvmAssert(obj->traits()->containsInterface(BUILTIN_TRAITS_(flash_utils_IExternalizable)));

            // !!@ Is it a worthwhile performance optimization to :
            //  1. create only one ObjectOutputObject for the stream?

            // invoke writeExternal()
            const int argc = 1;
            Atom argv[argc + 1];
            argv[0] = obj->atom();

            GCRef<ObjectOutputObject> output = toplevel->builtinClasses()->get_ObjectOutputClass()->constructObject();
            output->m_out = this;
            argv[1] = output->atom();

            MethodEnv* method = obj->vtable->methods[AvmCore::bindingToMethodId(info->get_functionBinding())];
            method->coerceEnter(argc, argv);

        }
        else
        {
            // for each sealed property in the class info, write out its value
            for (i = 0; i < count; i++)
            {
                WriteAtom(toplevel->getpropname(obj->atom(), info->SealedProp(i)));
            }

            if (dynamic)
            {
                GCRef<ScriptObject> writer = toplevel->builtinClasses()->get_ObjectEncodingClass()->m_writer;

                if (!writer)
                {
                    for (int index = 0; ((index = obj->nextNameIndex(index)) != 0);)
                    {
                        Atom name = obj->nextName(index);
                        Atom value = obj->nextValue(index);

                        if (AvmCore::istype(value, core->traits.function_itraits))
                        {
                            continue;
                        }

                        // Empty string is sentinel for end-of-data, so we CANNOT serialize it.
                        String *nameStr = core->string(name);
                        if (nameStr->length() == 0)
                        {
                            continue;
                        }
                        WriteString(nameStr);
                        WriteAtom(value);
                    }
                }
                else
                {
                    // invoke ObjectEncoding.dynamicPropertyWriter
                    GCRef<ScriptObject> func =  AvmCore::atomToScriptObject(toplevel->getpropname(writer->atom(), core->internConstantStringLatin1("writeDynamicProperties")));
                    GCRef<DynamicPropertyOutputObject> output = toplevel->builtinClasses()->get_DynamicPropertyOutputClass()->constructObject();
                    output->m_out = this;

                    const int argc = 2;
                    Atom argv[argc + 1];
                    argv[0] = writer->atom();
                    argv[1] = obj->atom();
                    argv[2] = output->atom();

                    func->call(argc, argv);
                }

                // Write out the empty string as a terminator
                WriteString(core->kEmptyString);
            }
        }
    }



// END class AvmPlusObjectOutput
//---------------------------------------------------------------------

//---------------------------------------------------------------------
// BEGIN class AvmPlusObjectInput
//
    uint32_t AvmPlusObjectInput::ReadUint29()
    {
        // Represent smaller integers with fewer bytes using the most
        // significant bit of each byte. The worst case uses 32-bits
        // to represent a 29-bit number, which is what we would have
        // done with no compression.

        // 0x00000000 - 0x0000007F : 0xxxxxxx
        // 0x00000080 - 0x00003FFF : 1xxxxxxx 0xxxxxxx
        // 0x00004000 - 0x001FFFFF : 1xxxxxxx 1xxxxxxx 0xxxxxxx
        // 0x00200000 - 0x3FFFFFFF : 1xxxxxxx 1xxxxxxx 1xxxxxxx xxxxxxxx
        // 0x40000000 - 0xFFFFFFFF : throw range exception

        uint8_t byte;
        uint32_t ref;

        byte = ReadU8();

        if (byte < 128)
        {
            return byte;
        }

        ref = (byte & 0x7F) << 7;
        byte = ReadU8();

        if (byte < 128)
        {
            return (ref | byte);
        }
    
        ref = (ref | (byte & 0x7F)) << 7;
        byte = ReadU8();

        if (byte < 128)
        {
            return (ref | byte);
        }

        ref = (ref | (byte & 0x7F)) << 8;
        byte = ReadU8();

        return (ref | byte);
    }

    Atom AvmPlusObjectInput::ReadAtom()
    {
        Toplevel* toplevel = this->toplevel();
        AvmCore* core = toplevel->core();

        uint8_t type = ReadType();

        Atom retVal;
        if (handleAdditionalType(type, retVal))
            return retVal;      // Handled by subclass. Nothing to be done here

        switch (type)
        {
            case kUndefinedAtomType:
                return undefinedAtom;
            case kNullAtomType:
                return nullObjectAtom;
            case kFalseAtomType:
                return falseAtom;
            case kTrueAtomType:
                return trueAtom;
            case kIntegerAtomType:
                // symmetric with writing an integer to fix sign bits for negative values
                return core->intToAtom(((int)ReadUint29() << 3) >> 3);
            case kDoubleAtomType:
                return core->doubleToAtom(ReadDouble());
#ifdef VMCFG_FLOAT
            case kFloatAtomType:
                return core->floatToAtom(ReadFloat());
            case kFloat4AtomType:
                return core->float4ToAtom(ReadFloat4());
#endif //VMCFG_FLOAT           
            case kStringAtomType:
                return ReadString()->atom();
            case kAvmMinusXmlAtomType:  //Even XML minus atoms are read back as XML objects. However handleAdditionalType can override this behavior.
            case kAvmPlusXmlAtomType:
                return ReadXML()->atom();
            case kDateAtomType:
                return ReadDate()->atom();
            case kArrayAtomType:
                return ReadArray()->atom();
            case kByteArrayAtomType:
                return ReadByteArray()->atom();
            case kObjectAtomType:
                return ReadScriptObject()->atom();
            case kDictionaryObject:
                return ReadDictionary()->atom();
            case kTypedVectorInt:
            case kTypedVectorUint:
            case kTypedVectorDouble:
            case kTypedVectorFloat:
            case kTypedVectorFloat4:
            case kTypedVectorObject:
                return ReadTypedVector(type);
            default:
                this->ThrowRangeError();
                return undefinedAtom;
        }
    }

    DateObject* AvmPlusObjectInput::ReadDate()
    {
        uint32_t ref = ReadReference();

        if ((ref & 1) == 0)
        {
            // This is a reference
            return ObjectListFind(ref >> 1).staticCast<DateObject>();
        }

        Toplevel* toplevel = this->toplevel();
        DateClass* dateClass = toplevel->dateClass();
        DateObject* obj = DateObject::create(dateClass->ivtable()->gc(), dateClass, Date(ReadDouble()));
        ObjectListAdd(obj);

        return obj;
    }

    GCRef<ByteArrayObject> AvmPlusObjectInput::ReadByteArray()
    {
        Toplevel* toplevel = this->toplevel();
        uint32_t ref = ReadReference();

        if ((ref & 1) == 0)
        {
            // This is a reference
            return ObjectListFind(ref >> 1).staticCast<ByteArrayObject>();
        }

        GCRef<ByteArrayObject> obj =  toplevel->byteArrayClass()->constructObject();
        ObjectListAdd(obj);

        int len = ref >> 1;

        ByteArray& ba = obj->GetByteArray();
        ba.SetLength(len);
        Read(ba.GetWritableBuffer(), len);

        return obj;
    }

    String *AvmPlusObjectInput::ReadString()
    {
        uint32_t ref = ReadReference();

        if ((ref & 1) == 0)
        {
            // This is a reference
            return StringListFind(ref >> 1);
        }

        // Read the string in
        uint32_t len = (ref >> 1);

        if (len == 0)
        {
            // WriteString() special cases the empty string to avoid creating a reference.
            return core()->kEmptyString;
        }

        char *buffer = mmfx_new_array_opt(char, len+1, MMgc::kCanFail);
        if (!buffer)
        {
            ThrowMemoryError();
        }

        Read(buffer, len);
        buffer[len] = 0;
        
        String *str = core()->newStringUTF8(buffer, len);
        mmfx_delete_array(buffer);

        StringListAdd(str);
        return str;
    }

    String* AvmPlusObjectInput::ReadXmlString(uint32_t len)
    {
        Toplevel* toplevel = this->toplevel();
        AvmCore* core = toplevel->core();
        char *buffer = mmfx_new_array_opt(char, len+1, MMgc::kCanFail);
        if (!buffer)
        {
            ThrowMemoryError();
        }

        Read(buffer, len);
        buffer[len] = 0;
        
        String *str = core->newStringUTF8(buffer, len);
        mmfx_delete_array(buffer);
        return str;
    }

    GCRef<ScriptObject> AvmPlusObjectInput::ReadXML()
    {
        uint32_t ref = ReadReference();

        if ((ref & 1) == 0)
        {
            // This is a reference
            return ObjectListFind(ref >> 1);
        }

        Toplevel* toplevel = this->toplevel();

        // Read the toString() version of the XML object in
        uint32_t len = (ref >> 1);

        String* str = ReadXmlString(len);

        // construct an XML object from the string
        GCRef<ScriptObject> obj;
        obj = toplevel->xmlClass()->constructObject(str->atom());

        ObjectListAdd(obj);

        return obj;
    }

    GCRef<DictionaryObject> AvmPlusObjectInput::ReadDictionary()
    {
        uint32_t ref = ReadReference();

        if ((ref & 1) == 0)
        {
            // This is a reference
            return ObjectListFind(ref >> 1).staticCast<DictionaryObject>();
        }

        Toplevel* toplevel = this->toplevel();
        AvmCore* core = toplevel->core();

        uint32_t len = ref >> 1;
        bool weakKeys = ReadBoolean();
        GCRef<DictionaryObject> obj = toplevel->builtinClasses()->get_DictionaryClass()->constructObject(weakKeys);
        ObjectListAdd(obj);

        for (uint32_t c = 0; c<len ; c++)
        {
            // Key can be either an object or an interned string
            Atom key = ReadAtom();
            // Value can be anything
            Atom value = ReadAtom();
            if (AvmCore::isString(key))
            {
                // Intern the string if not done already
                key = core->internString(key)->atom();
            }
            else if (!AvmCore::isObject(key))
            {
                // Keys can not be anything else
                ThrowArgumentError();
            }
            obj->setAtomProperty(key, value);
        }
        return obj;
    }

    Atom AvmPlusObjectInput::ReadTypedVector(uint8_t type)
    {
        uint32_t ref = ReadReference();

        if ((ref & 1) == 0)
        {
            // This is a reference
            return ObjectListFind(ref >> 1)->atom();
        }

        Toplevel* toplevel = this->toplevel();
        AvmCore* core = toplevel->core();

        uint32_t len = ref >> 1;
        bool fixed = ReadBoolean();
        
        switch (type)
        {
            case kTypedVectorInt:
            {
                GCRef<IntVectorObject> v = toplevel->intVectorClass()->newVector(len);
                ObjectListAdd(v);
                v->set_fixed(fixed);
                IntVectorAccessor va(v);
                int32_t* data = va.addr();
                for (uint32_t c=0; c<len; c++)
                {
                    data[c] = int32_t(ReadU32());
                }
                return v->atom();
            }
            case kTypedVectorUint:
            {
                GCRef<UIntVectorObject> v = toplevel->uintVectorClass()->newVector(len);
                ObjectListAdd(v);
                v->set_fixed(fixed);
                UIntVectorAccessor va(v);
                uint32_t* data = va.addr();
                for (uint32_t c=0; c<len; c++)
                {
                    data[c] = ReadU32();
                }
                return v->atom();
            }
            case kTypedVectorDouble:
            {
                GCRef<DoubleVectorObject> v = toplevel->doubleVectorClass()->newVector(len);
                ObjectListAdd(v);
                v->set_fixed(fixed);
                DoubleVectorAccessor va(v);
                double* data = va.addr();
                for (uint32_t c=0; c<len; c++)
                {
                    data[c] = ReadDouble();
                }
                return v->atom();
            }
#ifdef VMCFG_FLOAT
            case kTypedVectorFloat: 
            {
                GCRef<FloatVectorObject> v = toplevel->floatVectorClass()->newVector(len);
                ObjectListAdd(v);
                v->set_fixed(fixed);
                FloatVectorAccessor va(v);
                float* data = va.addr();
                for (uint32_t c=0; c<len; c++) 
                {
                    data[c] = ReadFloat();
                }
                return v->atom();
            }
            case kTypedVectorFloat4: 
            {
                GCRef<Float4VectorObject> v = toplevel->float4VectorClass()->newVector(len);
                ObjectListAdd(v);
                v->set_fixed(fixed);
                Float4VectorAccessor va(v);
                float4_t* data = va.addr();
                for (uint32_t c=0; c<len; c++) 
                {
                    data[c] = ReadFloat4();
                }
                return v->atom();
            }
#endif // VMCFG_FLOAT
            case kTypedVectorObject: 
            {
                // Read prototype
                Stringp classname = core->internString(ReadString());
                ClassClosure* closure = NULL;
                if (classname != core->kAsterisk)
                    closure = toplevel->getClassClosureFromAlias(classname);
                GCRef<ObjectVectorObject> v =  toplevel->vectorClass()->newVector(closure, len);
                ObjectListAdd(v);
                v->set_fixed(fixed);

                // Read contents
                for (uint32_t c=0; c<len; c++)
                {
                    v->setUintProperty(c,ReadAtom());
                }
                return v->atom();
            }
        }

        ThrowArgumentError();

        return 0;
    }

    GCRef<ScriptObject> AvmPlusObjectInput::ReadScriptObject(ClassClosure** closure)
    {
        uint32_t ref = ReadReference();

        if ((ref & 1) == 0)
        {
            // This object has already been in input, so (ref >> 1) is an index
            return ObjectListFind(ref >> 1);
        }

        // The object has not previously been in input, so we must read it in.
        // It begins with its class info

        Toplevel* toplevel = this->toplevel();
        AvmCore* core = toplevel->core();

        ClassInfo *info;

        if ((ref & 3) == 1)
        {
            // This ClassInfo has already been in input, so (ref >> 2) is an index
            info = ClassInfoListFind(ref >> 2);
        }
        else
        {
            // The ClassInfo has not previously been in input, so we must read it in.
            // dynamic, and the number of multinames to follow have been encoded
            // in the surplus bits of ref.
            bool externalizable = (ref & 4) ? true : false;
            bool dynamic = (ref & 8) ? true : false;
            int count = (ref >> 4);

            info = ClassInfo::Read(toplevel, this, dynamic, externalizable, count);
            ClassInfoListAdd(info);
        }

        // Create an object and add it to the list of objects already read in
        GCRef<ScriptObject> obj = info->closure()->constructObject();
        ObjectListAdd(obj);

        if (closure)
        {
            *closure = info->closure();
        }

        if (info->externalizable())
        {
            // ClassInfo::Read() has already verified that the class closure registered for this
            // class's alias implements IExternalizable, so there's no need to repeat the error handling here.
            AvmAssert(obj->traits()->containsInterface(BUILTIN_TRAITS_(flash_utils_IExternalizable)));

            // invoke readExternal()

            const int argc = 1;
            Atom argv[argc + 1];
            argv[0] = obj->atom();

            GCRef<ObjectInputObject> input = toplevel->builtinClasses()->get_ObjectInputClass()->constructObject();
            input->m_in = this;
            argv[1] = input->atom();

            MethodEnv* method = obj->vtable->methods[AvmCore::bindingToMethodId(info->get_functionBinding())];
            method->coerceEnter(argc, argv);
        }
        else
        {
            // read in the value of each sealed property, and then assign it to the object's member
            for (int i = 0, len = info->SealedPropCount(); i < len; i++)
            {
                SetObjectProperty(obj->atom(), info->SealedProp(i), ReadAtom());
            }
            
            // if the class is dynamic, read in the remaining properties (if any)
            if (info->dynamic())
            {
                for (;;)
                {
                    String* name = ReadString();
                    if (name->length() == 0)
                        break;

                    Atom value = ReadAtom();
                    SetObjectProperty(obj->atom(), core->internString(name), value);
                }
            }
        }

        return obj;
    }

    GCRef<ArrayObject> AvmPlusObjectInput::ReadArray()
    {
        AvmCore *core = this->core(); // this->core() is necessary when there is a local variable called core.

        uint32_t ref = ReadReference();

        if ((ref & 1) == 0)
        {
            // This is a reference
            return ObjectListFind(ref >> 1).staticCast<ArrayObject>();
        }

        uint32_t len = (ref >> 1);
        uint32_t i;

        Toplevel* toplevel = this->toplevel();
        GCRef<ArrayObject> obj =  toplevel->arrayClass()->newArray(len);
        ObjectListAdd(obj);

        // read the non-dense portion first, terminated by the empty string
        for (;;)
        {
            String* name = ReadString();
            if (name->length() == 0)
                break;

            Atom value = ReadAtom();
            SetObjectProperty(obj->atom(), core->internString(name), value);
        }

        // now read the dense portion.
        for (i = 0; i < len; i ++)
        {
            obj->setUintProperty(i, ReadAtom());
        }

        return obj;
    }
// END class AvmPlusObjectInput
//---------------------------------------------------------------------

//---------------------------------------------------------------------
// BEGIN class ClassInfo is used by class ObjectInput and ObjectOutput

    // for output, construct information from Traits

    bool ClassInfo::containsTransientMetadata(PoolObject* pool, const uint8_t* meta_pos)
    {
        AvmCore* core = m_toplevel->core();
        return meta_pos ? pool->hasMetadataName(meta_pos, core->internConstantStringLatin1(kTransient)) : false;
    }

    bool ClassInfo::slotContainsTransientMetadata(Traits *traits, int index)
    {
        PoolObject* pool = NULL;
        const uint8_t* pos = traits->getTraitsMetadata()->getSlotMetadataPos(index, pool);
        return containsTransientMetadata(pool, pos);
    }

    bool ClassInfo::methodContainsTransientMetadata(Traits *traits, int index)
    {
        PoolObject* pool = NULL;
        const uint8_t* pos = traits->getTraitsMetadata()->getMethodMetadataPos(index, pool);
        return containsTransientMetadata(pool, pos);
    }

    bool ClassInfo::isSerializable(Traits * t, Namespace* ns, Binding b)
    {
        // ignore members that are not public and not compatible with the current version
        if (!ns->isPublic() || !m_toplevel->core()->isNamespaceVisibleToApiVersionFromCallStack(ns))
        {
            return false;
        }

        if (AvmCore::isVarBinding(b))
        {
            uint32_t slot = AvmCore::bindingToSlotId(b);
            if (slotContainsTransientMetadata(t, slot))
            {
                // exclude slots contain metadata transient
                return false;
            }

        }
        else if (AvmCore::isAccessorBinding(b))
        {
            if (!AvmCore::hasGetterBinding(b) || methodContainsTransientMetadata(t, AvmCore::bindingToGetterId(b)))
            {
                // must implement a non-transient get accessor
                return false;
            }

            if (!AvmCore::hasSetterBinding(b) || methodContainsTransientMetadata(t, AvmCore::bindingToSetterId(b)))
            {
                // must implement a non-transient set accessor
                return false;
            }

        }
        else
        {
            // ignore members that aren't variables or accessors
            return false;
        }

        return true;
    }


    ClassInfo::ClassInfo(Toplevel* toplevel, Traits *t) :
        m_toplevel(toplevel),
        m_traits(t),
        m_dynamic(t->needsHashtable()),
        m_sealed(toplevel->core()->GetGC(), 64)
    {
        // initialize m_name
        m_name = toplevel->getAliasFromTraits(t);

        if (t->containsInterface(BUILTIN_TRAITS_(flash_utils_IExternalizable)))
        {
            if (m_name->isEmpty())
            {
                // readExternal() does not exist on class Object,
                // so it is meaningless to writeExternal() without a class name,
                // as an instance of Object will be created when we decode the data.

                // !!@ FIXME : Create a new exception for this?
                toplevel->argumentErrorClass()->throwError(kInvalidParamError);
            }
            
            AvmCore* core = toplevel->core();
            Multiname mn(core->getPublicNamespace(t->pool), core->internConstantStringLatin1(kWriteExternal));
            m_functionBinding = toplevel->getBinding(t, &mn);
            return;
        }

        TraitsIterator iterator(t);
        Stringp name;
        Namespace *ns;
        Binding b;

        // Add the name:String of all public variables to m_sealed.
        // For our purposes, a getter/setter is considered a variable too.
        while (iterator.getNext(name, ns, b))
        {
            if (isSerializable(t, ns, b))
            {
                m_sealed.add(name);
            }
        }
    }

    void ClassInfo::Write(AvmPlusObjectOutput *output)
    {
        int len = SealedPropCount();

        output->WriteString(m_name);

        for (int i = 0; i < len; i++)
        {
            // write the name:String for each property (SealedPropCount() was encoded in the invalid ref id
            output->WriteString(SealedProp(i));
        }
    }

    // for input, construct information from DataInput
    ClassInfo *ClassInfo::Read(Toplevel *toplevel, AvmPlusObjectInput *input, bool dynamic, bool externalizable, int count)
    {
        AvmCore* core = toplevel->core();
        ClassInfo *info = new (core->GetGC()) ClassInfo(toplevel);
        int i;

        info->m_dynamic = dynamic;
        info->m_name = core->internString(input->ReadString());

        // initialize m_closure
        info->m_closure = toplevel->getClassClosureFromAlias(info->m_name);

        if (externalizable)
        {
            if (!info->m_closure->traits()->itraits->containsInterface(BUILTIN_TRAITS_(flash_utils_IExternalizable)))
            {
                // object was encoded with writeExternal, but current class does not implement readExternal,
                // so data is meaningless to us.
                toplevel->argumentErrorClass()->throwError(kReadExternalNotImplementedError, info->m_name);
            }

            Multiname mn(core->findPublicNamespace(), core->internConstantStringLatin1(kReadExternal));
            info->m_functionBinding = toplevel->getBinding(info->m_closure->traits()->itraits, &mn);
        }

        info->m_traits = NULL; // unused when reading

        for (i = 0; i < count; i++)
        {
            String *name = input->ReadString();
            info->m_sealed.add(core->internString(name));
        }

        return info;
    }

// end class ClassInfo
//---------------------------------------------------------------------

}