root/core/TypeDescriber.cpp
/* [<][>][^][v][top][bottom][index][help] */
/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is [Open Source Virtual Machine.].
*
* The Initial Developer of the Original Code is
* Adobe System Incorporated.
* Portions created by the Initial Developer are Copyright (C) 2008
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Adobe AS3 Team
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "avmplus.h"
#include "BuiltinNatives.h"
#include "TypeDescriber.h"
using namespace MMgc;
namespace avmplus
{
TypeDescriber::TypeDescriber(Toplevel* toplevel) :
m_toplevel(toplevel)
{
VMPI_memset(m_strs, 0, sizeof(m_strs));
}
Stringp TypeDescriber::describeClassName(Traitsp traits)
{
if (!traits)
return str(kstrid_asterisk);
Stringp name = traits->formatClassName();
// no need to explicitly escape XML chars (eg > into >)
// since we're returning as JSON and converting to XML in AS3...
// the escaping will happen automatically by E4X infrastructure
return name;
}
ScriptObject* TypeDescriber::new_object()
{
return m_toplevel->objectClass->newInstance();
}
ArrayObject* TypeDescriber::new_array()
{
return (ArrayObject*)m_toplevel->arrayClass->newInstance();
}
void TypeDescriber::setpropmulti(ScriptObject* o, const TypeDescriber::KVPair* kv, uint32_t count)
{
while (count--)
{
o->setStringProperty(str(kv->keyid), kv->value);
++kv;
}
}
Stringp TypeDescriber::str(StringId i)
{
static const char* const k_strs[maxStringId] =
{
"access",
"accessors",
"*",
"bases",
"constructor",
"declaredBy",
"",
"interfaces",
"isDynamic",
"isFinal",
"isStatic",
"key",
"metadata",
"method",
"methods",
"name",
"native",
"optional",
"parameters",
"readonly",
"readwrite",
"returnType",
"traits",
"type",
"uri",
"value",
"variables",
"writeonly"
};
if (!m_strs[i])
m_strs[i] = m_toplevel->core()->internConstantStringLatin1(k_strs[i]);
return m_strs[i];
}
#define elem_count(o) (sizeof(o)/sizeof((o)[0]))
static void pushstr(ArrayObject* a, Stringp s)
{
a->setUintProperty(a->getLength(), s->atom());
}
static void pushobj(ArrayObject* a, ScriptObject* o)
{
a->setUintProperty(a->getLength(), o->atom());
}
static inline Atom strAtom(Stringp s)
{
return s ? s->atom() : nullStringAtom;
}
static inline Atom objAtom(ScriptObject* o)
{
return o ? o->atom() : nullObjectAtom;
}
static inline Atom boolAtom(bool b)
{
return b ? trueAtom : falseAtom;
}
ArrayObject* TypeDescriber::describeParams(MethodInfo* /*mi*/, MethodSignaturep ms)
{
ArrayObject* a = new_array();
const int requiredParamCount = ms->requiredParamCount();
for (int i = 1, n = ms->param_count(); i <= n; ++i)
{
ScriptObject* v = new_object();
const KVPair props[] = {
{ kstrid_type, strAtom(describeClassName(ms->paramTraits(i))) },
{ kstrid_optional, boolAtom(i > requiredParamCount) },
};
setpropmulti(v, props, elem_count(props));
pushobj(a, v);
}
return a;
}
void TypeDescriber::addBindings(AvmCore* core, MultinameHashtable* bindings, TraitsBindingsp tb, uint32_t flags)
{
if (!tb) return;
if ((flags & TypeDescriber::HIDE_OBJECT) && !tb->base) return;
addBindings(core, bindings, tb->base, flags);
StTraitsBindingsIterator iter(tb);
uint32_t curapi = core->getAPI(NULL);
while (iter.next())
{
if (!iter.key()) continue;
Namespacep ns = iter.ns();
if (ApiUtils::isVersionedNS(core, ns->getType(), ns->getURI())) {
// Skip names that don't match the current version
API api = iter.apis();
if (!(curapi & api)) {
continue;
}
}
bindings->add(iter.key(), iter.ns(), iter.value());
}
}
ScriptObject* TypeDescriber::describeTraits(Traitsp traits, uint32_t flags, Toplevel* toplevel)
{
if (!(flags & INCLUDE_TRAITS))
return NULL;
AvmCore* core = m_toplevel->core();
GC* gc = core->GetGC();
TraitsBindingsp tb = traits->getTraitsBindings();
TraitsMetadatap tm = traits->getTraitsMetadata();
ScriptObject* o = new_object();
ArrayObject* bases = NULL;
ArrayObject* metadata = NULL;
ArrayObject* interfaces = NULL;
ArrayObject* methods = NULL;
ArrayObject* accessors = NULL;
ArrayObject* variables = NULL;
ScriptObject* constructor = NULL;
if (flags & INCLUDE_METADATA)
{
metadata = new_array();
PoolObject* class_mdpool;
const uint8_t* class_md = tm->getMetadataPos(class_mdpool);
if (class_md)
addDescribeMetadata(metadata, class_mdpool, class_md);
}
if (flags & INCLUDE_BASES)
{
bases = new_array();
for (Traitsp b = traits->base; b; b = b->base)
pushstr(bases, describeClassName(b));
}
if (flags & INCLUDE_INTERFACES)
{
interfaces = new_array();
for (InterfaceIterator iter(traits); iter.hasNext();)
{
Traits* ti = iter.next();
pushstr(interfaces, describeClassName(ti));
}
}
// constructor
if (flags & INCLUDE_CONSTRUCTOR)
{
MethodInfo* initMethod = traits->init;
if (initMethod)
{
initMethod->resolveSignature(toplevel);
MethodSignaturep ms = initMethod->getMethodSignature();
if (ms->param_count() > 0)
{
constructor = describeParams(initMethod, ms);
}
}
}
if (flags & (INCLUDE_ACCESSORS | INCLUDE_METHODS | INCLUDE_VARIABLES))
{
// recover slot/method metadata and method-declarer information.
// make a flattened set of bindings so we don't have to check for overrides as we go.
// This is not terribly efficient, but doesn't need to be.
MultinameHashtable* mybind = new (gc) MultinameHashtable();
addBindings(m_toplevel->core(), mybind, tb, flags);
// Don't want interface methods, so post-process and wipe out any
// bindings that were added.
for (InterfaceIterator ifc_iter(traits); ifc_iter.hasNext();)
{
Traitsp ti = ifc_iter.next();
TraitsBindingsp tbi = ti->getTraitsBindings();
StTraitsBindingsIterator iter(tbi);
while (iter.next())
{
if (!iter.key()) continue;
mybind->add(iter.key(), iter.ns(), BIND_NONE);
}
}
// yuck, replicate buggy behavior in FP9/10
List<Namespacep> nsremoval(gc);
if (flags & HIDE_NSURI_METHODS)
{
for (TraitsBindingsp tbi = tb->base; tbi; tbi = tbi->base)
{
StTraitsBindingsIterator iter(tbi);
while (iter.next())
{
if (!iter.key()) continue;
Namespacep ns = iter.ns();
if (ns->getURI()->length() > 0 && nsremoval.indexOf(ns) < 0)
{
nsremoval.add(ns);
}
}
}
}
StMNHTIterator iter(mybind);
while (iter.next())
{
if (!iter.key()) continue;
Stringp name = iter.key();
Namespacep ns = iter.ns();
Binding binding = iter.value();
Stringp nsuri = ns->getURI();
TraitsMetadata::MetadataPtr md1 = NULL;
TraitsMetadata::MetadataPtr md2 = NULL;
PoolObject* md1pool = NULL;
PoolObject* md2pool = NULL;
// We only display public members -- exposing private namespaces could compromise security.
if (ns->getType() != Namespace::NS_Public) {
continue;
}
if ((flags & HIDE_NSURI_METHODS) && nsremoval.indexOf(ns) >= 0) {
continue;
}
ScriptObject* v = new_object();
const BindingKind bk = AvmCore::bindingKind(binding);
switch (bk)
{
case BKIND_CONST:
case BKIND_VAR:
{
if (!(flags & INCLUDE_VARIABLES))
continue;
const uint32_t slotID = AvmCore::bindingToSlotId(binding);
const KVPair props[] = {
{ kstrid_access, strAtom(str(bk == BKIND_CONST ? kstrid_readonly : kstrid_readwrite)) },
{ kstrid_type, strAtom(describeClassName(tb->getSlotTraits(slotID))) },
};
setpropmulti(v, props, elem_count(props));
if (!variables) variables = new_array();
pushobj(variables, v);
md1 = tm->getSlotMetadataPos(slotID, md1pool);
break;
}
case BKIND_METHOD:
{
if (!(flags & INCLUDE_METHODS))
continue;
const uint32_t methodID = AvmCore::bindingToMethodId(binding);
MethodInfo* mi = tb->getMethod(methodID);
mi->resolveSignature(toplevel);
MethodSignaturep ms = mi->getMethodSignature();
Traitsp declaringTraits = mi->declaringTraits();
const KVPair props[] = {
{ kstrid_declaredBy, strAtom(describeClassName(declaringTraits)) },
{ kstrid_returnType, strAtom(describeClassName(ms->returnTraits())) },
{ kstrid_parameters, objAtom(describeParams(mi, ms)) },
};
setpropmulti(v, props, elem_count(props));
if (!methods) methods = new_array();
pushobj(methods, v);
md1 = tm->getMethodMetadataPos(methodID, md1pool);
break;
}
case BKIND_GET:
case BKIND_SET:
case BKIND_GETSET:
{
if (!(flags & INCLUDE_ACCESSORS))
continue;
const uint32_t methodID = AvmCore::hasGetterBinding(binding) ?
AvmCore::bindingToGetterId(binding) :
AvmCore::bindingToSetterId(binding);
MethodInfo* mi = tb->getMethod(methodID);
mi->resolveSignature(toplevel);
MethodSignaturep ms = mi->getMethodSignature();
Traitsp declaringTraits = mi->declaringTraits();
Traitsp accessorType = AvmCore::hasGetterBinding(binding) ?
ms->returnTraits() :
ms->paramTraits(1);
static const uint8_t bk2str[8] =
{
uint8_t(kstrid_emptyString), // BKIND_NONE
uint8_t(kstrid_emptyString), // BKIND_METHOD
uint8_t(kstrid_emptyString), // BKIND_VAR
uint8_t(kstrid_emptyString), // BKIND_CONST
uint8_t(kstrid_emptyString), // unused
uint8_t(kstrid_readonly), // BKIND_GET
uint8_t(kstrid_writeonly), // BKIND_SET
uint8_t(kstrid_readwrite) // BKIND_GETSET
};
const KVPair props[] = {
{ kstrid_declaredBy, strAtom(describeClassName(declaringTraits)) },
{ kstrid_access, strAtom(str(StringId(bk2str[bk]))) },
{ kstrid_type, strAtom(describeClassName(accessorType)) },
};
setpropmulti(v, props, elem_count(props));
if (AvmCore::hasGetterBinding(binding))
md1 = tm->getMethodMetadataPos(AvmCore::bindingToGetterId(binding), md1pool);
if (AvmCore::hasSetterBinding(binding))
md2 = tm->getMethodMetadataPos(AvmCore::bindingToSetterId(binding), md2pool);
if (!accessors) accessors = new_array();
pushobj(accessors, v);
break;
}
case BKIND_NONE:
break;
}
ArrayObject* vm = NULL;
if ((flags & INCLUDE_METADATA) && (md1 || md2))
{
vm = new_array();
addDescribeMetadata(vm, md1pool, md1);
addDescribeMetadata(vm, md2pool, md2);
}
const KVPair props[] = {
{ kstrid_name, strAtom(name) },
{ kstrid_uri, strAtom(nsuri->length() == 0 ? NULL : nsuri) },
{ kstrid_metadata, objAtom(vm) },
};
setpropmulti(v, props, elem_count(props));
}
}
const KVPair props[] = {
{ kstrid_bases, objAtom(bases) },
{ kstrid_interfaces, objAtom(interfaces) },
{ kstrid_metadata, objAtom(metadata) },
{ kstrid_accessors, objAtom(accessors) },
{ kstrid_methods, objAtom(methods) },
{ kstrid_variables, objAtom(variables) },
{ kstrid_constructor, objAtom(constructor) },
};
setpropmulti(o, props, elem_count(props));
return o;
}
void TypeDescriber::addDescribeMetadata(ArrayObject* a, PoolObject* pool, const uint8_t* meta_pos)
{
if (meta_pos)
{
uint32_t metadata_count = AvmCore::readU30(meta_pos);
while (metadata_count--)
{
const uint32_t metadata_index = AvmCore::readU30(meta_pos);
ScriptObject* md = describeMetadataInfo(pool, metadata_index);
if (md)
pushobj(a, md);
}
}
}
static void read_u30_list(List<uint32_t>& list, uint32_t val_count, const uint8_t*& pos)
{
list.ensureCapacity(val_count);
while (val_count--)
{
list.add(AvmCore::readU30(pos));
}
}
ScriptObject* TypeDescriber::describeMetadataInfo(PoolObject* pool, uint32_t metadata_index)
{
AvmCore* core = m_toplevel->core();
const uint8_t* metadata_pos = pool->metadata_infos[metadata_index];
const uint32_t name_index = (metadata_pos) ? AvmCore::readU30(metadata_pos) : 0;
// A bit of a hack: if the pool is builtin, always omit metadata chunks with names of "Version"
// or "native", since these are used for reserved purposes internally.
Stringp name = poolstr(pool, name_index);
AvmAssert(name->isInterned() && core->kVersion->isInterned() && str(kstrid_native)->isInterned());
if (pool->isBuiltin && (name == core->kVersion || name == str(kstrid_native)))
return NULL;
const uint32_t val_count = (metadata_pos) ? AvmCore::readU30(metadata_pos) : 0;
ScriptObject* o = new_object();
ArrayObject* a = new_array();
if (val_count > 0)
{
GC* gc = core->GetGC();
List<uint32_t> key_indexes(gc);
List<uint32_t> val_indexes(gc);
read_u30_list(key_indexes, val_count, metadata_pos);
read_u30_list(val_indexes, val_count, metadata_pos);
for (uint32_t i = 0; i < val_count; ++i)
{
ScriptObject* v = new_object();
const KVPair props[] = {
{ kstrid_key, strAtom(poolstr(pool, key_indexes.get(i))) },
{ kstrid_value, strAtom(poolstr(pool, val_indexes.get(i))) },
};
setpropmulti(v, props, elem_count(props));
pushobj(a, v);
}
}
const KVPair props[] = {
{ kstrid_name, strAtom(name) },
{ kstrid_value, objAtom(a) },
};
setpropmulti(o, props, elem_count(props));
return o;
}
Stringp TypeDescriber::poolstr(PoolObject* pool, uint32_t index)
{
return index < pool->constantStringCount ? pool->getString(index) : str(kstrid_emptyString);
}
// bug comaptibility note: FP10 and earlier sniffed the Atom type and return 'int' for
// all integer atoms. This is wrong, but we should preserve the behavior. Note that we
// compare the numeric range: doesn't matter for 32-bit builds, but 64-builds have larger
// possible int atoms, so this preserves the existing behavior of "29-bit int -> int" even
// on 64-bit systems.
static bool isIntAtom29Bit(Atom value)
{
if (atomIsIntptr(value))
{
intptr_t const i = atomGetIntptr(value);
int32_t const i32 = (int32_t(i)<<3)>>3;
return i == intptr_t(i32);
}
return false;
}
Traits* TypeDescriber::chooseTraits(Atom value, uint32_t flags)
{
Traitsp traits;
if (value == undefinedAtom)
traits = m_toplevel->core()->traits.void_itraits;
else if (ISNULL(value))
traits = m_toplevel->core()->traits.null_itraits;
else if (isIntAtom29Bit(value))
traits = m_toplevel->core()->traits.int_itraits;
else
traits = m_toplevel->toTraits(value);
if (flags & USE_ITRAITS)
traits = traits->itraits;
return traits;
}
ScriptObject* TypeDescriber::describeType(Atom value, uint32_t flags)
{
Traitsp traits = chooseTraits(value, flags);
if (!traits)
return NULL;
ScriptObject* o = new_object();
const KVPair props[] = {
{ kstrid_name, strAtom(describeClassName(traits)) },
{ kstrid_isDynamic, boolAtom(traits->needsHashtable()) },
{ kstrid_isFinal, boolAtom(traits->final) },
{ kstrid_isStatic, boolAtom(traits->itraits != NULL) },
{ kstrid_traits, objAtom(describeTraits(traits, flags, m_toplevel)) },
};
setpropmulti(o, props, elem_count(props));
return o;
}
Stringp TypeDescriber::getQualifiedClassName(Atom value)
{
Traitsp traits = chooseTraits(value, 0);
return traits ? describeClassName(traits) : NULL;
}
Stringp TypeDescriber::getQualifiedSuperclassName(Atom value)
{
// getQualifiedSuperclassName explicitly allows us to pass Class or Instance,
// and either should resolve to super of Instance
Traitsp traits = chooseTraits(value, 0);
if (!traits)
return NULL;
if (traits->itraits)
traits = traits->itraits;
return traits->base && traits->base != m_toplevel->core()->traits.class_itraits ?
describeClassName(traits->base) :
NULL;
}
}