Mozilla/mozilla/js/tamarin/core/MethodEnv.cpp
wsharp%adobe.com 039e5a464d bug 375561. stejohns+ review. Various fixes from Flash player codebase
git-svn-id: svn://10.0.0.236/trunk@222472 18797224-902f-48f8-a5cc-f745e15eee43
2007-03-27 18:37:45 +00:00

1608 lines
44 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ***** 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) 1993-2006
* 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"
namespace avmplus
{
#undef DEBUG_EARLY_BINDING
Atom MethodEnv::coerceEnter(int argc, Atom* atomv)
{
Toplevel* toplevel = vtable->toplevel;
if (!method->argcOk(argc))
{
toplevel->argumentErrorClass()->throwError(kWrongArgumentCountError, core()->toErrorString((AbstractFunction*)method), core()->toErrorString(method->requiredParamCount()), core()->toErrorString(argc));
}
AbstractFunction* method = this->method;
// just do enough to resolve signatures. Don't do a full verify yet.
method->resolveSignature(toplevel);
// check receiver type first
// caller will coerce instance if necessary,
// so make sure it was done.
AvmAssert(atomv[0] == toplevel->coerce(atomv[0], method->paramTraits(0)));
// now unbox everything, including instance and rest args
int extra = argc > method->param_count ? argc - method->param_count : 0;
AvmAssert(method->restOffset > 0 && extra >= 0);
uint32 *ap = (uint32 *) alloca(method->restOffset + sizeof(Atom)*extra);
unboxCoerceArgs(argc, atomv, ap);
// we know we have verified the method, so we can go right into it.
Traits* returnType = method->returnTraits();
AvmCore* core = this->core();
if (returnType == NUMBER_TYPE)
{
AvmAssert(method->implN != NULL);
double d = method->implN(this, argc, ap);
return core->doubleToAtom(d);
}
else
{
AvmAssert(method->impl32 != NULL);
Atom i = method->impl32(this, argc, ap);
if (returnType == INT_TYPE)
return core->intToAtom(i);
else if (returnType == UINT_TYPE)
return core->uintToAtom((uint32)i);
else if (returnType == BOOLEAN_TYPE)
return i ? trueAtom : falseAtom;
else if (!returnType || returnType == OBJECT_TYPE || returnType == VOID_TYPE)
return (Atom)i;
else if (returnType == STRING_TYPE)
return ((Stringp)i)->atom();
else if (returnType == NAMESPACE_TYPE)
return ((Namespace*)i)->atom();
else
return ((ScriptObject*)i)->atom();
}
}
/**
* convert atoms to native args. argc is the number of
* args, not counting the instance which is arg[0]. the
* layout is [instance][arg1..argN]
*/
void MethodEnv::unboxCoerceArgs(int argc, Atom* in, uint32 *argv)
{
AbstractFunction* f = this->method;
AvmCore* core = this->core();
Toplevel* toplevel = this->toplevel();
Atom *args = (Atom *) argv;
for (int i=0; i <= argc; i++)
{
if (i <= f->param_count)
{
Traits* t = f->paramTraits(i);
AvmAssert(t != VOID_TYPE);
if (t == NUMBER_TYPE)
{
union {
double d;
uint32 l[2];
};
d = core->number(in[i]);
#ifdef AVMPLUS_64BIT
AvmAssert(sizeof(Atom) == sizeof(double));
*(double *) args = d;
args++;
#else
AvmAssert(sizeof(Atom) * 2 == sizeof(double));
*args++ = l[0];
*args++ = l[1];
#endif
}
else if (t == INT_TYPE)
{
*args++ = core->integer(in[i]);
}
else if (t == UINT_TYPE)
{
*args++ = core->toUInt32(in[i]);
}
else if (t == BOOLEAN_TYPE)
{
*args++ = core->boolean(in[i]);
}
else if (t == OBJECT_TYPE)
{
*args++ = in[i] == undefinedAtom ? nullObjectAtom : in[i];
}
else if (!t)
{
*args++ = in[i];
}
else
{
// ScriptObject, String, or Namespace, or Null
*args++ = toplevel->coerce(in[i],t) & ~7;
}
}
else
{
*args++ = in[i];
}
}
}
Atom MethodEnv::delegateInvoke(MethodEnv* env, int argc, uint32 *ap)
{
env->impl32 = env->method->impl32;
return env->impl32(env, argc, ap);
}
MethodEnv::MethodEnv(void *addr, VTable *vtable)
: vtable(vtable), method(NULL)
{
typedef Atom (*AtomMethodProc)(MethodEnv*, int, uint32 *);
impl32 = *(AtomMethodProc*) &addr;
}
MethodEnv::MethodEnv(AbstractFunction* method, VTable *vtable)
: vtable(vtable), method(method)
{
// make the first call go to the method impl
impl32 = delegateInvoke;
AvmCore* core = vtable->traits->core;
#ifdef AVMPLUS_VERBOSE
if (method->declaringTraits != vtable->traits)
{
core->console << "ERROR " << method->name << " " << method->declaringTraits << " " << vtable->traits << "\n";
}
#endif
if(method->declaringTraits != vtable->traits){
AvmAssertMsg(0, "(method->declaringTraits != vtable->traits)");
toplevel()->throwVerifyError(kCorruptABCError);
}
if (method->flags & AbstractFunction::NEED_ACTIVATION)
{
// This can happen when the ABC has MethodInfo data but not MethodBody data
if (!method->activationTraits)
{
toplevel()->throwVerifyError(kCorruptABCError);
}
VTable *activation = core->newVTable(method->activationTraits, NULL, vtable->scope, vtable->abcEnv, toplevel());
activation->resolveSignatures();
setActivationOrMCTable(activation, kActivation);
}
// register this env in the callstatic method table
int method_id = method->method_id;
if (method_id != -1)
{
AbcEnv* abcEnv = vtable->abcEnv;
AvmAssert(abcEnv->pool == (PoolObject *) method->pool);
if (abcEnv->methods[method_id] == NULL)
{
abcEnv->setMethod(method_id, this);
}
#ifdef AVMPLUS_VERBOSE
else if (method->pool->verbose)
{
core->console << "WARNING: tried to re-register global MethodEnv for " << method << "\n";
}
#endif
}
}
#ifdef DEBUGGER
void MethodEnv::debugEnter(int argc, uint32 *ap,
Traits**frameTraits, int localCount,
CallStackNode* callstack,
Atom* framep, volatile sintptr *eip)
{
AvmCore* core = this->core();
// update profiler
sendEnter(argc, ap);
// dont reset the parameter traits since they are setup in the prologue
int firstLocalAt = method->param_count+1;
AvmAssert(!frameTraits || localCount >= firstLocalAt);
if (frameTraits) memset(&frameTraits[firstLocalAt], 0, (localCount-firstLocalAt)*sizeof(Traits*));
if (callstack) callstack->initialize(this, method, framep, frameTraits, argc, ap, eip);
if (core->debugger) core->debugger->_debugMethod(this);
core->sampleCheck();
}
void MethodEnv::debugExit(CallStackNode* callstack)
{
AvmAssert(this != 0);
AvmCore* core = this->core();
// update profiler
sendExit();
core->callStack = callstack->next;
// trigger a faked "line number changed" since we exited the function and are now back on the old line (well, almost)
if (core->callStack && core->callStack->linenum > 0)
{
int line = core->callStack->linenum;
core->callStack->linenum = -1;
if (core->debugger) core->debugger->debugLine(line);
}
}
void MethodEnv::sendEnter(int /*argc*/, uint32 * /*ap*/)
{
Profiler *profiler = core()->profiler;
if (profiler->profilingDataWanted && !core()->sampler()->sampling)
profiler->sendFunctionEnter(method);
}
void MethodEnv::sendExit()
{
Profiler *profiler = core()->profiler;
if (profiler->profilingDataWanted && !core()->sampler()->sampling)
profiler->sendFunctionExit();
}
#endif
void MethodEnv::nullcheck(Atom atom)
{
if (!AvmCore::isNullOrUndefined(atom))
return;
// TypeError in ECMA
ErrorClass *error = toplevel()->typeErrorClass();
if( error ){
error->throwError(
(atom == undefinedAtom) ? kConvertUndefinedToObjectError :
kConvertNullToObjectError);
} else {
toplevel()->throwVerifyError(kCorruptABCError);
}
}
void MethodEnv::npe()
{
toplevel()->throwTypeError(kConvertNullToObjectError);
}
void MethodEnv::interrupt()
{
vtable->traits->core->interrupt(this);
}
Traits* MethodEnv::toClassITraits(Atom atom)
{
switch (atom&7)
{
case kObjectType:
{
if( !AvmCore::isNull(atom) )
{
Traits* itraits = AvmCore::atomToScriptObject(atom)->traits()->itraits;
if (itraits == NULL)
toplevel()->throwTypeError(kIsTypeMustBeClassError);
return itraits;
}
// else fall through and report an error
}
default:
// TypeError in ECMA
// ISSUE the error message should say "whatever" is not a class
toplevel()->throwTypeError(
(atom == undefinedAtom) ? kConvertUndefinedToObjectError :
kConvertNullToObjectError);
return NULL;
}
}
ArrayObject* MethodEnv::createRest(Atom* argv, int argc)
{
// create arguments Array using argv[param_count..argc]
Atom* extra = argv + method->param_count + 1;
int extra_count = argc > method->param_count ? argc - method->param_count : 0;
return toplevel()->arrayClass->newarray(extra, extra_count);
}
#ifdef AVMPLUS_MIR
Atom MethodEnv::getpropertyHelper(Atom obj, Multiname *multi, VTable *vtable, Atom index)
{
if ((index&7) == kIntegerType)
{
return getpropertylate_i(obj, index>>3);
}
if ((index&7) == kDoubleType)
{
int i = AvmCore::integer_i(index);
if ((double)i == AvmCore::atomToDouble(index))
{
return getpropertylate_i(obj, i);
}
}
if (AvmCore::isObject(index))
{
ScriptObject* i = AvmCore::atomToScriptObject(index);
if (i->traits() == core()->traits.qName_itraits)
{
QNameObject* qname = (QNameObject*) i;
qname->getMultiname(*multi);
}
else if(!multi->isRtns() && core()->isDictionary(obj))
{
return AvmCore::atomToScriptObject(obj)->getAtomProperty(index);
}
else
{
multi->setName(core()->intern(index));
}
}
else
{
multi->setName(core()->intern(index));
}
return toplevel()->getproperty(obj, multi, vtable);
}
void MethodEnv::initpropertyHelper(Atom obj, Multiname *multi, Atom value, VTable *vtable, Atom index)
{
if ((index&7) == kIntegerType)
{
setpropertylate_i(obj, index>>3, value);
return;
}
if ((index&7) == kDoubleType)
{
int i = core()->integer(index);
uint32 u = (uint32)(i);
if ((double)u == AvmCore::atomToDouble(index))
{
setpropertylate_u(obj, u, value);
return;
}
else if ((double)i == AvmCore::atomToDouble(index))
{
setpropertylate_i(obj, i, value);
return;
}
}
if (AvmCore::isObject(index))
{
ScriptObject* i = AvmCore::atomToScriptObject(index);
if (i->traits() == core()->traits.qName_itraits)
{
QNameObject* qname = (QNameObject*) i;
qname->getMultiname(*multi);
}
else
{
multi->setName(core()->intern(index));
}
}
else
{
multi->setName(core()->intern(index));
}
initproperty(obj, multi, value, vtable);
}
void MethodEnv::setpropertyHelper(Atom obj, Multiname *multi, Atom value, VTable *vtable, Atom index)
{
if ((index&7) == kIntegerType)
{
setpropertylate_i(obj, index>>3, value);
return;
}
if ((index&7) == kDoubleType)
{
int i = core()->integer(index);
uint32 u = (uint32)(i);
if ((double)u == AvmCore::atomToDouble(index))
{
setpropertylate_u(obj, u, value);
return;
}
else if ((double)i == AvmCore::atomToDouble(index))
{
setpropertylate_i(obj, i, value);
return;
}
}
if (AvmCore::isObject(index))
{
ScriptObject* i = AvmCore::atomToScriptObject(index);
if (i->traits() == core()->traits.qName_itraits)
{
QNameObject* qname = (QNameObject*) i;
qname->getMultiname(*multi);
}
else if(!multi->isRtns() && core()->isDictionary(obj))
{
AvmCore::atomToScriptObject(obj)->setAtomProperty(index, value);
return;
}
else
{
multi->setName(core()->intern(index));
}
}
else
{
multi->setName(core()->intern(index));
}
toplevel()->setproperty(obj, multi, value, vtable);
}
Atom MethodEnv::delpropertyHelper(Atom obj, Multiname *multi, Atom index)
{
AvmCore* core = this->core();
if (AvmCore::isObject(obj) && AvmCore::isObject(index))
{
if( core->isXMLList(index) )
{
// Error according to E4X spec, section 11.3.1
toplevel()->throwTypeError(kDeleteTypeError, core->toErrorString(toplevel()->toTraits(index)));
}
ScriptObject* i = AvmCore::atomToScriptObject(index);
if (i->traits() == core->traits.qName_itraits)
{
QNameObject* qname = (QNameObject*) i;
qname->getMultiname(*multi);
}
else if(!multi->isRtns() && core->isDictionary(obj))
{
bool res = AvmCore::atomToScriptObject(obj)->deleteAtomProperty(index);
return res ? trueAtom : falseAtom;
}
else
{
multi->setName(core->intern(index));
}
}
else
{
multi->setName(core->intern(index));
}
return delproperty(obj, multi);
}
void MethodEnv::initMultinameLateForDelete(Multiname& name, Atom index)
{
AvmCore *core = this->core();
if (AvmCore::isObject(index))
{
if (core->isXMLList(index))
{
// Error according to E4X spec, section 11.3.1
toplevel()->throwTypeError(kDeleteTypeError, core->toErrorString(toplevel()->toTraits(index)));
}
ScriptObject* i = AvmCore::atomToScriptObject(index);
if (i->traits() == core->traits.qName_itraits)
{
QNameObject* qname = (QNameObject*) i;
bool attr = name.isAttr();
qname->getMultiname(name);
if (attr)
name.setAttr(attr);
return;
}
}
name.setName(core->intern(index));
}
ScriptObject* MethodEnv::newcatch(Traits* traits)
{
AvmCore* core = this->core();
Toplevel* toplevel = this->toplevel();
if (traits == core->traits.object_itraits)
{
// don't need temporary vtable. this is a scope for a finally clause
// todo: asc shouldn't even call OP_newcatch in a finally clause
return toplevel->objectClass->construct();
}
else
{
VTable *vt = core->newVTable(traits, NULL, vtable->scope, vtable->abcEnv, toplevel);
vt->resolveSignatures();
return core->newObject(vt, NULL);
}
}
ArrayObject* MethodEnv::createArgumentsHelper(int argc, uint32 *ap)
{
// create arguments using argv[1..argc].
// Even tho E3 says create an Object, E4 says create an Array so thats what we will do.
AvmAssert(argc >= 0);
Atom* atomv = (Atom*) alloca((argc+1) * sizeof(Atom));
method->boxArgs(argc, ap, atomv);
return createArguments(atomv, argc);
}
ArrayObject* MethodEnv::createRestHelper(int argc, uint32 *ap)
{
// create rest Array using argv[param_count..argc]
Atom* extra = (Atom*) (method->restOffset + (char*)ap);
int extra_count = argc > method->param_count ? argc - method->param_count : 0;
return toplevel()->arrayClass->newarray(extra, extra_count);
}
#endif // AVMPLUS_MIR
Atom MethodEnv::getpropertylate_i(Atom obj, int index) const
{
// here we put the case for bind-none, since we know there are no bindings
// with numeric names.
if ((obj&7) == kObjectType)
{
if (index >= 0)
{
// try dynamic lookup on instance. even if the traits are sealed,
// we might need to search the prototype chain
return AvmCore::atomToScriptObject(obj)->getUintProperty(index);
}
else
{
// negative - we must intern the integer
return AvmCore::atomToScriptObject(obj)->getAtomProperty(method->core()->internInt(index)->atom());
}
}
else
{
// primitive types are not dynamic, so we can go directly
// to their __proto__ object
AvmCore* core = method->core();
Toplevel *toplevel = this->toplevel();
ScriptObject *protoObject = toplevel->toPrototype(obj);
return protoObject->ScriptObject::getStringPropertyFromProtoChain(core->internInt(index), protoObject, toplevel->toTraits(obj));
}
}
Atom MethodEnv::getpropertylate_u(Atom obj, uint32 index) const
{
// here we put the case for bind-none, since we know there are no bindings
// with numeric names.
if ((obj&7) == kObjectType)
{
// try dynamic lookup on instance. even if the traits are sealed,
// we might need to search the prototype chain
return AvmCore::atomToScriptObject(obj)->getUintProperty(index);
}
else
{
// primitive types are not dynamic, so we can go directly
// to their __proto__ object
AvmCore* core = method->core();
Toplevel *toplevel = this->toplevel();
ScriptObject *protoObject = toplevel->toPrototype(obj);
return protoObject->ScriptObject::getStringPropertyFromProtoChain(core->internUint32(index), protoObject, toplevel->toTraits(obj));
}
}
ScriptObject* MethodEnv::finddef(Multiname* multiname) const
{
Toplevel* toplevel = vtable->toplevel;
ScriptEnv* script = getScriptEnv(multiname);
if (script == (ScriptEnv*)BIND_AMBIGUOUS)
toplevel->throwReferenceError(kAmbiguousBindingError, multiname);
if (script == (ScriptEnv*)BIND_NONE)
toplevel->throwReferenceError(kUndefinedVarError, multiname);
ScriptObject* global = script->global;
if (!global)
{
global = script->initGlobal();
Atom argv[1] = { global->atom() };
script->coerceEnter(0, argv);
}
return global;
}
ScriptEnv* MethodEnv::getScriptEnv(Multiname *multiname) const
{
ScriptEnv *se = (ScriptEnv*)abcEnv()->domainEnv->getScriptInit(multiname);
if(!se)
{
// check privates
se = (ScriptEnv*)abcEnv()->privateScriptEnvs.getMulti(multiname);
}
return se;
}
ScriptObject* MethodEnv::finddefNsset(NamespaceSet* nsset, Stringp name) const
{
Multiname m(nsset);
m.setName(name);
return finddef(&m);
}
ScriptObject* MethodEnv::finddefNs(Namespace* ns, Stringp name) const
{
Multiname m(ns, name);
return finddef(&m);
}
ScriptObject* ScriptEnv::initGlobal()
{
// object not defined yet. define it by running the script that exports it
Traits* traits = vtable->traits;
vtable->resolveSignatures();
NativeScriptInfo* nativeEntry = traits->getNativeScriptInfo();
Toplevel* toplevel = this->toplevel();
traits->resolveSignatures(toplevel);
ScriptObject* delegate = toplevel->objectClass->prototype;
if (nativeEntry != NULL)
{
// special script with native impl object
return global = nativeEntry->handler(vtable, delegate);
}
else
{
// ordinary user script
return global = (ScriptObject*) method->core()->newObject(vtable, delegate);
}
}
ScriptObject* MethodEnv::op_newobject(Atom* sp, int argc) const
{
// pre-size the hashtable since we know how many vars are coming
VTable* object_vtable = toplevel()->object_vtable;
AvmCore* core = method->core();
ScriptObject* o = new (core->GetGC(), object_vtable->getExtraSize())
ScriptObject(object_vtable, toplevel()->objectClass->prototype,
2*argc+1);
for (; argc-- > 0; sp -= 2)
{
Atom name = sp[-1];
//verifier should ensure names are String
//todo have the verifier take care of interning too
AvmAssert(AvmCore::isString(name));
o->setAtomProperty(core->internString(name)->atom(), sp[0]);
}
return o;
}
Atom MethodEnv::nextname(Atom objAtom, int index) const
{
if (index <= 0)
return nullStringAtom;
switch (objAtom&7)
{
case kObjectType:
return AvmCore::atomToScriptObject(objAtom)->nextName(index);
case kNamespaceType:
return AvmCore::atomToNamespace(objAtom)->nextName(method->core(), index);
default:
ScriptObject* proto = toplevel()->toPrototype(objAtom); // cn: types like Number are sealed, but their prototype could have dynamic properties
return proto ? proto->nextName(index) : undefinedAtom;
}
}
Atom MethodEnv::nextvalue(Atom objAtom, int index) const
{
if (index <= 0)
return undefinedAtom;
switch (objAtom&7)
{
case kObjectType:
return AvmCore::atomToScriptObject(objAtom)->nextValue(index);
case kNamespaceType:
return AvmCore::atomToNamespace(objAtom)->nextValue(index);
default:
ScriptObject* proto = toplevel()->toPrototype(objAtom);
return (proto ? proto->nextValue(index) : undefinedAtom);
}
}
int MethodEnv::hasnext(Atom objAtom, int index) const
{
if (index < 0)
return 0;
if (!AvmCore::isNullOrUndefined(objAtom))
{
switch (objAtom&7)
{
case kObjectType:
return AvmCore::atomToScriptObject(objAtom)->nextNameIndex(index);
case kNamespaceType:
return AvmCore::atomToNamespace(objAtom)->nextNameIndex(index);
default:
ScriptObject* proto = toplevel()->toPrototype(objAtom);
int nextIndex = ( proto ? proto->nextNameIndex(index) : 0);
return nextIndex;
}
}
else
{
return 0;
}
}
int MethodEnv::hasnext2(Atom& objAtom, int& index) const
{
if (index < 0)
return 0;
ScriptObject *delegate = NULL;
if (!AvmCore::isNullOrUndefined(objAtom))
{
switch (objAtom&7)
{
case kObjectType:
{
ScriptObject *object = AvmCore::atomToScriptObject(objAtom);
delegate = object->getDelegate();
index = object->nextNameIndex(index);
}
break;
case kNamespaceType:
{
index = AvmCore::atomToNamespace(objAtom)->nextNameIndex(index);
delegate = toplevel()->namespaceClass->prototype;
}
break;
default:
{
ScriptObject* proto = toplevel()->toPrototype(objAtom);
delegate = proto ? proto->getDelegate() : NULL;
index = ( proto ? proto->nextNameIndex(index) : 0);
}
}
}
else
{
index = 0;
}
while (index == 0 && delegate != NULL)
{
// Advance to next object on prototype chain
ScriptObject *object = delegate;
objAtom = object->atom();
delegate = object->getDelegate();
index = object->nextNameIndex(index);
}
if (index == 0)
{
// If we're done, set object local to null
objAtom = nullObjectAtom;
}
return index != 0;
}
Atom MethodEnv::in(Atom nameatom, Atom obj) const
{
AvmCore* core = method->core();
Traits *t = toplevel()->toTraits(obj); // includes null check
if(!core->isDictionaryLookup(nameatom, obj))
{
Stringp name = core->intern(nameatom);
// ISSUE should we try this on each object on the proto chain or just the first?
if (t->findBinding(name, core->publicNamespace) != BIND_NONE)
{
return trueAtom;
}
nameatom = name->atom();
}
ScriptObject* o = (obj&7)==kObjectType ? AvmCore::atomToScriptObject(obj) :
toplevel()->toPrototype(obj);
do
{
if (o->hasAtomProperty(nameatom))
return trueAtom;
}
while ((o = o->getDelegate()) != NULL);
return falseAtom;
}
// see 13.2 creating function objects
ClassClosure* MethodEnv::newfunction(AbstractFunction *function,
ScopeChain* outer,
Atom* scopes) const
{
AvmCore* core = this->core();
AbcEnv* abcEnv = vtable->abcEnv;
// TODO: if we have already created a function and the scope chain
// is the same as last time, re-use the old closure?
// declaringTraits must have been filled in by verifier.
Traits* ftraits = function->declaringTraits;
AvmAssert(ftraits != NULL);
AvmAssert(ftraits->scope != NULL);
ScopeChain* scope = ScopeChain::create(core->GetGC(), ftraits->scope, outer, *core->dxnsAddr);
for (int i=outer->getSize(), n=scope->getSize(); i < n; i++)
{
scope->setScope(i, *scopes++);
}
FunctionClass* functionClass = toplevel()->functionClass;
// the vtable for the new function object
VTable* fvtable = core->newVTable(ftraits, functionClass->ivtable(), scope, abcEnv, toplevel());
fvtable->resolveSignatures();
FunctionEnv *fenv = new (core->GetGC()) FunctionEnv(function, fvtable);
fvtable->call = fenv;
fvtable->ivtable = toplevel()->object_vtable;
ClassClosure* c = new (core->GetGC(), fvtable->getExtraSize()) ClassClosure(fvtable);
c->setDelegate( functionClass->prototype );
c->createVanillaPrototype();
c->prototype->setStringProperty(core->kconstructor, c->atom());
c->prototype->setStringPropertyIsEnumerable(core->kconstructor, false);
fenv->closure = c;
return c;
}
/**
* given a classInfo, create a new ClassClosure object and return it on the stack.
*/
ClassClosure* MethodEnv::newclass(AbstractFunction* cinit,
ClassClosure *base,
ScopeChain* outer,
Atom* scopes) const
{
AvmCore* core = this->core();
Toplevel* toplevel = this->toplevel();
Traits* ctraits = cinit->declaringTraits;
Traits* itraits = ctraits->itraits;
// finish resolving the base class
if (!base && itraits->base)
{
// class has a base but no base object was provided
ErrorClass *error = toplevel->typeErrorClass();
if( error )
error->throwError(kConvertNullToObjectError);
else
toplevel->throwTypeError(kCorruptABCError);
}
// make sure the traits of the base vtable matches the base traits
if (!(base == NULL && itraits->base == NULL || base != NULL && itraits->base == base->ivtable()->traits))
{
ErrorClass *error = toplevel->verifyErrorClass();
if( error )
error->throwError(kInvalidBaseClassError);
else
toplevel->throwTypeError(kCorruptABCError);
}
ctraits->resolveSignatures(toplevel);
itraits->resolveSignatures(toplevel);
// class scopechain = [..., class]
ScopeChain* cscope = ScopeChain::create(core->GetGC(), ctraits->scope, outer, *core->dxnsAddr);
int staticScopesCount = 0;
int i = outer->getSize();
for (int n=cscope->getSize()-staticScopesCount; i < n; i++)
{
cscope->setScope(i, *scopes++);
}
ScopeChain* iscope = ScopeChain::create(core->GetGC(), itraits->scope, cscope, *core->dxnsAddr);
AbcEnv *abcEnv = vtable->abcEnv;
VTable* cvtable = core->newVTable(ctraits, toplevel->class_vtable, cscope, abcEnv, toplevel);
cvtable->resolveSignatures();
VTable* ivtable = core->newVTable(itraits, base ? base->ivtable() : NULL, iscope, abcEnv, toplevel);
ivtable->resolveSignatures();
cvtable->ivtable = ivtable;
if (itraits == core->traits.object_itraits) {
// we just defined Object
toplevel->object_vtable = ivtable;
}
else if (itraits == core->traits.class_itraits) {
// we just defined Class
toplevel->class_vtable = ivtable;
cvtable->base = ivtable;
toplevel->objectClass->vtable->base = ivtable;
}
NativeClassInfo* nativeEntry;
ClassClosure *cc;
if ((nativeEntry = cvtable->traits->getNativeClassInfo()) != NULL)
{
cc = nativeEntry->handler(cvtable);
}
else
{
cc = new (core->GetGC(), cvtable->getExtraSize()) ClassClosure(cvtable);
AvmAssert(cc->prototype == NULL);
cc->createVanillaPrototype();
}
if (cc->prototype)
{
// C.prototype.__proto__ = Base.prototype
if (base != NULL)
cc->prototype->setDelegate( base->prototype );
// C.prototype.constructor = C {DontEnum}
cc->prototype->setStringProperty(core->kconstructor, cc->atom());
cc->prototype->setStringPropertyIsEnumerable(core->kconstructor, false);
}
AvmAssert(i == iscope->getSize()-1);
iscope->setScope(i, cc->atom());
if (toplevel->classClass)
{
cc->setDelegate( toplevel->classClass->prototype );
}
// Invoke the class init function.
Atom argv[1] = { cc->atom() };
cvtable->init->coerceEnter(0, argv);
return cc;
}
void MethodEnv::initproperty(Atom obj, Multiname* multiname, Atom value, VTable* vtable) const
{
Toplevel* toplevel = this->toplevel();
Binding b = toplevel->getBinding(vtable->traits, multiname);
if ((b&7) == BIND_CONST)
{
if (vtable->init != this)
toplevel->throwReferenceError(kConstWriteError, multiname, vtable->traits);
b = (b & ~7) | BIND_VAR;
}
toplevel->setproperty_b(obj, multiname, value, vtable, b);
}
void MethodEnv::setpropertylate_i(Atom obj, int index, Atom value) const
{
if (AvmCore::isObject(obj))
{
ScriptObject* o = AvmCore::atomToScriptObject(obj);
if (index >= 0)
{
o->setUintProperty(index, value);
}
else
{
// negative index - we must intern the integer
o->setAtomProperty(method->core()->internInt(index)->atom(), value);
}
}
else
{
// obj represents a primitive Number, Boolean, int, or String, and primitives
// are sealed and final. Cannot add dynamic vars to them.
// throw a ReferenceError exception — Property not found and could not be created.
Multiname tempname(core()->publicNamespace, core()->internInt(index));
toplevel()->throwReferenceError(kWriteSealedError, &tempname, toplevel()->toTraits(obj));
}
}
void MethodEnv::setpropertylate_u(Atom obj, uint32 index, Atom value) const
{
if (AvmCore::isObject(obj))
{
AvmCore::atomToScriptObject(obj)->setUintProperty(index, value);
}
else
{
// obj represents a primitive Number, Boolean, int, or String, and primitives
// are sealed and final. Cannot add dynamic vars to them.
// throw a ReferenceError exception — Property not found and could not be created.
Multiname tempname(core()->publicNamespace, core()->internUint32(index));
toplevel()->throwReferenceError(kWriteSealedError, &tempname, toplevel()->toTraits(obj));
}
}
Atom MethodEnv::callsuper(Multiname* multiname, int argc, Atom* atomv) const
{
VTable* base = vtable->base;
Toplevel* toplevel = this->toplevel();
Binding b = toplevel->getBinding(base->traits, multiname);
switch (b&7)
{
default:
toplevel->throwReferenceError(kCallNotFoundError, multiname, base->traits);
case BIND_METHOD:
{
#ifdef DEBUG_EARLY_BINDING
core()->console << "callsuper method " << base->traits << " " << multiname->name << "\n";
#endif
MethodEnv* superenv = base->methods[AvmCore::bindingToMethodId(b)];
return superenv->coerceEnter(argc, atomv);
}
case BIND_VAR:
case BIND_CONST:
{
#ifdef DEBUG_EARLY_BINDING
core()->console << "callsuper slot " << base->traits << " " << multiname->name << "\n";
#endif
uint32 slot = AvmCore::bindingToSlotId(b);
Atom method = AvmCore::atomToScriptObject(atomv[0])->getSlotAtom(slot);
return toplevel->op_call(method, argc, atomv);
}
case BIND_SET:
{
#ifdef DEBUG_EARLY_BINDING
core()->console << "callsuper setter " << base->traits << " " << multiname->name << "\n";
#endif
// read on write-only property
toplevel->throwReferenceError(kWriteOnlyError, multiname, base->traits);
}
case BIND_GET:
case BIND_GETSET:
{
#ifdef DEBUG_EARLY_BINDING
core()->console << "callsuper getter " << base->traits << " " << multiname->name << "\n";
#endif
// Invoke the getter
int m = AvmCore::bindingToGetterId(b);
MethodEnv *f = base->methods[m];
Atom atomv_out[1] = { atomv[0] };
Atom method = f->coerceEnter(0, atomv_out);
return toplevel->op_call(method, argc, atomv);
}
}
}
Atom MethodEnv::delproperty(Atom obj, Multiname* multiname) const
{
Toplevel* toplevel = this->toplevel();
Traits* traits = toplevel->toTraits(obj); // includes null check
if (AvmCore::isObject(obj))
{
Binding b = toplevel->getBinding(traits, multiname);
if (b == BIND_NONE)
{
bool b = AvmCore::atomToScriptObject(obj)->deleteMultinameProperty(multiname);
return b ? trueAtom : falseAtom;
}
else if (AvmCore::isMethodBinding(b))
{
if (multiname->contains(core()->publicNamespace) && toplevel->isXmlBase(obj))
{
// dynamic props should hide declared methods on delete
ScriptObject* so = AvmCore::atomToScriptObject(obj);
bool b = so->deleteMultinameProperty(multiname);
return b ? trueAtom : falseAtom;
}
}
}
else
{
toplevel->throwReferenceError(kDeleteSealedError, multiname, traits);
}
return falseAtom;
}
Atom MethodEnv::getsuper(Atom obj, Multiname* multiname) const
{
VTable* vtable = this->vtable->base;
Toplevel* toplevel = this->toplevel();
Binding b = toplevel->getBinding(vtable->traits, multiname);
switch (b&7)
{
default:
toplevel->throwReferenceError(kReadSealedError, multiname, vtable->traits);
case BIND_METHOD:
{
#ifdef DEBUG_EARLY_BINDING
core->console << "getsuper method " << vtable->traits << " " << multiname->name << "\n";
#endif
// extracting a virtual method
MethodEnv *m = vtable->methods[AvmCore::bindingToMethodId(b)];
return toplevel->methodClosureClass->create(m, obj)->atom();
}
case BIND_VAR:
case BIND_CONST:
#ifdef DEBUG_EARLY_BINDING
core->console << "getsuper slot " << vtable->traits << " " << multiname->name << "\n";
#endif
return AvmCore::atomToScriptObject(obj)->getSlotAtom(AvmCore::bindingToSlotId(b));
case BIND_SET:
{
#ifdef DEBUG_EARLY_BINDING
core->console << "getsuper setter " << vtable->traits << " " << multiname->name << "\n";
#endif
// read on write-only property
toplevel->throwReferenceError(kWriteOnlyError, multiname, vtable->traits);
}
case BIND_GET:
case BIND_GETSET:
{
#ifdef DEBUG_EARLY_BINDING
core->console << "getsuper getter " << vtable->traits << " " << multiname->name << "\n";
#endif
// Invoke the getter
int m = AvmCore::bindingToGetterId(b);
MethodEnv *f = vtable->methods[m];
Atom atomv_out[1] = { obj };
return f->coerceEnter(0, atomv_out);
}
}
}
void MethodEnv::setsuper(Atom obj, Multiname* multiname, Atom value) const
{
VTable* vtable = this->vtable->base;
Toplevel* toplevel = this->toplevel();
Binding b = toplevel->getBinding(vtable->traits, multiname);
switch (b&7)
{
default:
toplevel->throwReferenceError(kWriteSealedError, multiname, vtable->traits);
case BIND_CONST:
toplevel->throwReferenceError(kConstWriteError, multiname, vtable->traits);
case BIND_METHOD:
toplevel->throwReferenceError(kCannotAssignToMethodError, multiname, vtable->traits);
case BIND_GET:
#ifdef DEBUG_EARLY_BINDING
core()->console << "setsuper getter " << vtable->traits << " " << multiname->name << "\n";
#endif
toplevel->throwReferenceError(kConstWriteError, multiname, vtable->traits);
case BIND_VAR:
#ifdef DEBUG_EARLY_BINDING
core()->console << "setsuper slot " << vtable->traits << " " << multiname->name << "\n";
#endif
AvmCore::atomToScriptObject(obj)->setSlotAtom(AvmCore::bindingToSlotId(b), value);
return;
case BIND_SET:
case BIND_GETSET:
{
#ifdef DEBUG_EARLY_BINDING
core()->console << "setsuper setter " << vtable->traits << " " << multiname->name << "\n";
#endif
// Invoke the setter
uint32 m = AvmCore::bindingToSetterId(b);
AvmAssert(m < vtable->traits->methodCount);
MethodEnv* method = vtable->methods[m];
Atom atomv_out[2] = { obj, value };
// coerce value to proper type, then call enter
method->coerceEnter(1, atomv_out);
return;
}
}
}
Atom MethodEnv::findWithProperty(Atom atom, Multiname* multiname)
{
Toplevel* toplevel = this->toplevel();
if ((atom&7)==kObjectType)
{
// usually scope objects are ScriptObject's
ScriptObject* o = AvmCore::atomToScriptObject(atom);
// search the delegate chain for a value. we must look at
// traits at each step along the way in case we have class
// instances on the scope chain
do
{
// ISSUE it is incorrect to return a reference to an object on the prototype
// chain, we should only return the scopechain object; the next operation
// could be a setproperty, and we don't want to mutate prototype objects this way.
// look at the traits first, stop if found.
Binding b = toplevel->getBinding(o->traits(), multiname);
if (b != BIND_NONE)
return atom;
if (o->hasMultinameProperty(multiname))
return atom;
}
while ((o = o->getDelegate()) != NULL);
}
else
{
// primitive value on scope chain!
// first iteration looks at traits only since primitive values don't have
// dynamic variables
Binding b = toplevel->getBinding(toplevel->toTraits(atom), multiname);
if (b != BIND_NONE)
return atom;
// then we continue starting at the prototype for this primitive type
ScriptObject* o = toplevel->toPrototype(atom);
do
{
Binding b = toplevel->getBinding(o->traits(), multiname);
if (b != BIND_NONE)
return atom;
if (o->hasMultinameProperty(multiname))
return atom;
}
while ((o = o->getDelegate()) != NULL);
}
return nullObjectAtom;
}
/**
* return the object on the scope chain that owns the given property.
* if no object has that property, return scope[0]. we search each
* delegate chain but only return objects that are on the scope chain.
* this way, find+get and find+set are both correct. find+set should
* not mutate a prototype object.
*/
Atom MethodEnv::findproperty(ScopeChain* outer,
Atom* scopes,
int extraScopes,
Multiname* multiname,
bool strict,
Atom* withBase)
{
Toplevel* toplevel = this->toplevel();
// look in each with frame in the current stack frame
Atom* scopep = &scopes[extraScopes-1];
if (withBase)
{
for (; scopep >= withBase; scopep--)
{
Atom result = findWithProperty(*scopep, multiname);
if (!AvmCore::isNull(result))
{
return result;
}
}
}
// if we're in global$init (outer_depth==0), don't check "this" scope just yet.
int outer_depth = outer->getSize();
for (; scopep > scopes; scopep--)
{
Atom a = *scopep;
Traits* t = toplevel->toTraits(a);
Binding b = toplevel->getBinding(t, multiname);
if (b != BIND_NONE)
return a;
}
ScopeTypeChain* outerTraits = outer->scopeTraits;
if (outer_depth > 0 && scopep >= scopes)
{
// consider "this" scope now, but constrain it to the declaringTraits of
// the current method (verifier ensures this is safe)
Atom a = *scopep;
Traits *t;
if (outerTraits->fullsize > outerTraits->size)
{
// scope traits has extra constraint for "this" scope, see OP_newclass in verifier
t = outerTraits->scopes[outerTraits->size].traits;
}
else
{
// "this" scope type is the runtime type
t = toplevel->toTraits(a);
}
Binding b = toplevel->getBinding(t, multiname);
if (b != BIND_NONE)
return a;
}
// now search outer scopes
for (int i=outer_depth-1; i > 0; i--)
{
if (outerTraits->scopes[i].isWith)
{
Atom result = findWithProperty(outer->getScope(i), multiname);
if (!AvmCore::isNull(result))
return result;
}
else
{
// only look at the properties on the captured (verify time) type, not the actual type,
// of the outer scope object.
Atom a = outer->getScope(i);
Traits* t = outerTraits->scopes[i].traits;
Binding b = toplevel->getBinding(t, multiname);
if (b != BIND_NONE)
return a;
}
}
// No imported definitions or global scope can have attributes. (Using filter
// operator with non existent attribute will get here.
if (multiname->isAttr())
{
if (strict)
toplevel->throwReferenceError(kUndefinedVarError, multiname);
return undefinedAtom;
}
// now we have searched all the scopes, except global
// look for imported definition (similar logic to OP_finddef). This will
// find definitions in this script and in other scripts.
ScriptEnv* script = getScriptEnv(multiname);
if (script != (ScriptEnv*)BIND_NONE)
{
if (script == (ScriptEnv*)BIND_AMBIGUOUS)
toplevel->throwReferenceError(kAmbiguousBindingError, multiname);
ScriptObject* global = script->global;
if (global == NULL)
{
global = script->initGlobal();
Atom argv[1] = { script->global->atom() };
script->coerceEnter(0, argv);
}
return global->atom();
}
// no imported definition found. look for dynamic props
// on the global object
ScriptObject* global = AvmCore::atomToScriptObject(
outer_depth > 0 ? outer->getScope(0) : *scopes
);
// search the delegate chain for a value. The delegate
// chain for the global object will only contain vanilla
// objects (Object.prototype) so we can skip traits
ScriptObject* o = global;
do
{
if (o->hasMultinameProperty(multiname))
return global->atom();
}
while ((o = o->getDelegate()) != NULL);
// If a variable cannot be found
// throw reference error if strict
if (strict)
toplevel->throwReferenceError(kUndefinedVarError, multiname);
return global->atom();
}
Namespace* MethodEnv::internRtns(Atom nsAtom)
{
if (((nsAtom&7) != kNamespaceType) || AvmCore::isNull(nsAtom))
toplevel()->throwTypeError(kIllegalNamespaceError);
return core()->internNamespace(AvmCore::atomToNamespace(nsAtom));
}
ArrayObject* MethodEnv::createArguments(Atom *atomv, int argc)
{
Toplevel* toplevel = this->toplevel();
ArrayObject *arguments = toplevel->arrayClass->newarray(atomv+1,argc);
ScriptObject *closure;
if (method->flags & AbstractFunction::NEED_CLOSURE)
{
closure = toplevel->methodClosureClass->create(this, atomv[0]);
}
else
{
closure = ((FunctionEnv*)this)->closure;
}
arguments->setStringProperty(core()->kcallee, closure->atom());
arguments->setStringPropertyIsEnumerable(core()->kcallee, false);
return arguments;
}
Atom MethodEnv::getdescendants(Atom obj, Multiname* multiname)
{
if (AvmCore::isObject (obj))
{
return core()->atomToScriptObject(obj)->getDescendants (multiname);
}
else
{
// Rhino simply returns undefined for other Atom types
// SpiderMonkey throws TypeError. We're doing TypeError.
toplevel()->throwTypeError(kDescendentsError, core()->toErrorString(toplevel()->toTraits(obj)));
return undefinedAtom; // not reached
}
}
Atom MethodEnv::getdescendantslate(Atom obj, Atom index, bool attr)
{
if (AvmCore::isObject(index))
{
ScriptObject* i = AvmCore::atomToScriptObject(index);
if (i->traits() == core()->traits.qName_itraits)
{
QNameObject* qname = (QNameObject*) i;
Multiname n2;
qname->getMultiname(n2);
if (attr)
n2.setAttr(attr);
return getdescendants(obj, &n2);
}
}
// convert index to string
AvmCore* core = this->core();
Multiname name(core->publicNamespace, core->intern(index));
if (attr)
name.setAttr();
return getdescendants(obj, &name);
}
void MethodEnv::checkfilter(Atom obj)
{
if ( !toplevel()->isXmlBase(obj) )
{
toplevel()->throwTypeError(kFilterError, core()->toErrorString(toplevel()->toTraits(obj)));
}
}
/**
* implements ECMA implicit coersion. returns the coerced value,
* or throws a TypeError if coersion is not possible.
*/
ScriptObject* MethodEnv::coerceAtom2SO(Atom atom, Traits* expected) const
{
AvmAssert(expected != NULL);
AvmAssert(!expected->isMachineType);
AvmAssert(expected != core()->traits.string_itraits);
AvmAssert(expected != core()->traits.namespace_itraits);
if (AvmCore::isNullOrUndefined(atom))
return NULL;
ScriptObject *so;
if ((atom&7) == kObjectType &&
(so=AvmCore::atomToScriptObject(atom))->traits()->containsInterface(expected))
{
return so;
}
else
{
// failed
#ifdef AVMPLUS_VERBOSE
//core->console << "checktype failed " << expected << " <- " << atom << "\n";
#endif
toplevel()->throwTypeError(kCheckTypeFailedError, core()->atomToErrorString(atom), core()->toErrorString(expected));
return NULL;
}
}
/**
* implements ECMA as operator. Returns the same value, or null.
*/
Atom MethodEnv::astype(Atom atom, Traits* expected)
{
return core()->istype(atom, expected) ? atom : nullObjectAtom;
}
VTable *MethodEnv::getActivation()
{
int type = getType();
switch(type)
{
case kActivation:
return (VTable *)(activationOrMCTable&~3);
case kActivationMethodTablePair:
return getPair()->activation;
default:
return NULL;
}
}
WeakKeyHashtable *MethodEnv::getMethodClosureTable()
{
int type = getType();
if(!activationOrMCTable)
{
WeakKeyHashtable *wkh = new (core()->GetGC()) WeakKeyHashtable(core()->GetGC());
setActivationOrMCTable(wkh, kMethodTable);
return wkh;
}
else if(type == kActivation)
{
WeakKeyHashtable *wkh = new (core()->GetGC()) WeakKeyHashtable(core()->GetGC());
ActivationMethodTablePair *amtp = new (core()->GetGC()) ActivationMethodTablePair(getActivation(), wkh);
setActivationOrMCTable(amtp, kActivationMethodTablePair);
return wkh;
}
else if(type == kActivationMethodTablePair)
{
return getPair()->methodTable;
}
return (WeakKeyHashtable*)(activationOrMCTable&~3);
}
}