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