/* -*- 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 { void MethodEnv::argcError(int32_t argc) { MethodSignaturep ms = get_ms(); AvmAssert(!ms->argcOk(argc)); toplevel()->argumentErrorClass()->throwError(kWrongArgumentCountError, core()->toErrorString(method), core()->toErrorString(ms->requiredParamCount()), core()->toErrorString(argc)); } MethodEnv::MethodEnv(MethodInfo* method, ScopeChain* scope) : method(method), _scope(scope), activationOrMCTable(0) { AvmAssert(method != NULL); core()->exec->init(this); if (method->declaringTraits() != this->vtable()->traits) { #ifdef AVMPLUS_VERBOSE core()->console << "ERROR " << method->getMethodName() << " " << method->declaringTraits() << " " << this->vtable()->traits << "\n"; #endif AvmAssertMsg(0, "(method->declaringTraits != vtable->traits)"); toplevel()->throwVerifyError(kCorruptABCError); } // activation info used to be created here, but is now created lazily } #ifdef DEBUGGER void MethodEnv::debugEnter( const uint8_t* frame_sst, CallStackNode* callstack, FramePtr framep, volatile intptr_t *eip) { AvmAssert(this != 0); AvmAssert(callstack != 0); callstack->init(this, framep, frame_sst, eip); debugEnterInner(); } void MethodEnv::debugEnterInner() { AvmAssert(this != 0); AvmCore* core = this->core(); Sampler* s = core->get_sampler(); // this shouldn't ever be called unless there's a debugger AvmAssert(core->debugger() != NULL); Debugger* debugger = core->debugger(); if (debugger) debugger->_debugMethod(this); if (s) s->sampleCheck(); // method_id can legitimately be -1 for activations, but we don't care about those here, // so just ignore them. const int32_t method_id = this->method->method_id(); if (method_id >= 0) { this->abcEnv()->invocationCount(method_id) += 1; // returns a reference, so this works } } void MethodEnv::debugExit(CallStackNode* callstack) { AvmAssert(this != 0); AvmAssert(callstack != 0); AvmCore* core = this->core(); Sampler* s = core->get_sampler(); if (s != 0) { // do this before popping the stack, so that the time slice // gets attributed to the function that is exiting s->sampleCheck(); } 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) { int32_t line = core->callStack->linenum(); core->callStack->set_linenum(-1); Debugger* debugger = core->debugger(); if (debugger) debugger->debugLine(line); } } uint64_t MethodEnv::invocationCount() const { const int32_t method_id = this->method->method_id(); if (method_id < 0) return 0; return this->abcEnv()->invocationCount(method_id); } #endif // DEBUGGER void FASTCALL MethodEnv::nullcheckfail(Atom atom) { AvmAssert(AvmCore::isNullOrUndefined(atom)); // TypeError in ECMA Toplevel* toplevel = this->toplevel(); GCRef error = toplevel->typeErrorClass(); if( error ){ error->throwError( (atom == undefinedAtom) ? kConvertUndefinedToObjectError : kConvertNullToObjectError); } else { toplevel->throwVerifyError(kCorruptABCError); } } ArrayObject* MethodEnv::createRest(Atom* argv, int32_t argc) { // create arguments Array using argv[param_count..argc] MethodSignaturep ms = get_ms(); const int32_t param_count = ms->param_count(); Atom* extra = argv + param_count + 1; const int32_t extra_count = argc > param_count ? argc - param_count : 0; return toplevel()->arrayClass()->newarray(extra, extra_count); } #if defined VMCFG_NANOJIT || defined VMCFG_AOT Atom MethodEnv::getpropertyHelper(Atom obj, /* not const */ Multiname *multi, VTable *vtable, Atom index) { if (atomIsIntptr(index) && atomCanBeInt32(index)) { return getpropertylate_i(obj, (int32_t)atomGetIntptr(index)); } if (atomKind(index) == kDoubleType) { return getpropertylate_d(obj, AvmCore::atomToDouble(index)); } 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() && AvmCore::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, /* not const */ Multiname *multi, Atom value, VTable *vtable, Atom index) { if (atomIsIntptr(index) && atomCanBeInt32(index)) { setpropertylate_i(obj, (int32_t)atomGetIntptr(index), value); return; } if (atomKind(index) == kDoubleType) { setpropertylate_d(obj, AvmCore::atomToDouble(index), 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, /* not const */ Multiname *multi, Atom value, VTable *vtable, Atom index) { // the positive-integer case is inlined into setprop_index in jit-calls.h, // this handles the negative case if (atomIsIntptr(index) && atomCanBeInt32(index)) { setpropertylate_i(obj, (int32_t)atomGetIntptr(index), value); return; } if (atomKind(index) == kDoubleType) { setpropertylate_d(obj, AvmCore::atomToDouble(index), 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() && AvmCore::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, /* not const */ Multiname *multi, Atom index) { AvmCore* core = this->core(); if (AvmCore::isObject(obj) && AvmCore::isObject(index)) { if (AvmCore::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() && AvmCore::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 (AvmCore::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)); } #endif 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, toplevel); ScopeChain* catchScope = this->scope()->cloneWithNewVTable(core->GetGC(), vt, this->abcEnv()); vt->resolveSignatures(catchScope); return core->newObject(vt, NULL); } } #ifdef VMCFG_NANOJIT ArrayObject* MethodEnv::createArgumentsHelper(int argc, uint32_t *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); MMgc::GC::AllocaAutoPtr _atomv; Atom* atomv = (Atom*) avmStackAllocArray(core(), _atomv, (argc+1), sizeof(Atom)); method->getMethodSignature()->boxArgs(core(), argc, ap, atomv); return createArguments(atomv, argc); } ArrayObject* MethodEnv::createRestHelper(int argc, uint32_t *ap) { // create rest Array using argv[param_count..argc] MethodSignaturep ms = get_ms(); const int rest_offset = ms->rest_offset(); union { char* extra_8; Atom* extra; }; extra_8 = (char*)ap + rest_offset; const int param_count = ms->param_count(); const int extra_count = argc > param_count ? argc - param_count : 0; return toplevel()->arrayClass()->newarray(extra, extra_count); } #endif // VMCFG_NANOJIT // Keep in sync with related methods. See comments at Toplevel::getproperty. /*bool*/ int32_t MethodEnv::haspropertylate_u(Atom obj, uint32_t index) const { #ifdef VMCFG_FLOAT if (AvmCore::isFloat4(obj)) { // See FIXME in Toplevel::getproperty() - do not absorb non-integer values outside // the range [0,2^32-2], but let them pass through to the prototype lookup. if (index != 4294967295U) return int32_t(index <= 3); } #endif // Primitive types are not dynamic, so we can go directly // to their __proto__ object. ScriptObject *o = (atomKind(obj) == kObjectType) ? AvmCore::atomToScriptObject(obj) : this->toplevel()->toPrototype(obj); bool present = o->hasUintProperty(index); return (int32_t)present; } // Keep in sync with related methods. See comments at Toplevel::getproperty. /*bool*/ int32_t MethodEnv::haspropertylate_i(Atom obj, int32_t index) const { #ifdef VMCFG_FLOAT if (AvmCore::isFloat4(obj)) { // See FIXME in Toplevel::getproperty() - do not absorb non-integer values outside // the range [0,2^32-2], but let them pass through to the prototype lookup. if (index >= 0) return int32_t(index <= 3); } #endif // Primitive types are not dynamic, so we can go directly // to their __proto__ object. ScriptObject *o = (atomKind(obj) == kObjectType) ? AvmCore::atomToScriptObject(obj) : this->toplevel()->toPrototype(obj); bool present; if (index >= 0) { present = o->hasUintProperty((uint32_t)index); } else { Atom interned = this->core()->internInt(index)->atom(); present = o->hasAtomProperty(interned); } return (int32_t)present; } // Numeric property name. Except for float4, no fixed bindings have numeric names. // Keep in sync with related methods. See comments at Toplevel::getproperty. Atom MethodEnv::getpropertylate_i(Atom obj, int32_t index) const { if (atomKind(obj) == 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(this->core()->internInt(index)->atom()); } } #ifdef VMCFG_FLOAT else if (AvmCore::isFloat4(obj)) { if (index >= 0 && index <= 3){ float4_t f4 = AvmCore::atomToFloat4(obj); float* pf4 = reinterpret_cast(&f4); return core()->floatToAtom(pf4[index]); } // See FIXME in Toplevel::getproperty() - do not absorb non-integer values outside // the range [0,2^32-2], but let them pass through to the prototype lookup. if (index >= 0) toplevel()->throwRangeError(kOutOfRangeError, this->core()->internInt(index)); } #endif // *No* else clause { // Primitive types are not dynamic, so we can go directly // to their __proto__ object. AvmCore* core = this->core(); Toplevel *toplevel = this->toplevel(); ScriptObject *protoObject = toplevel->toPrototype(obj); return protoObject->ScriptObject::getStringPropertyFromProtoChain(core->internInt(index), protoObject, toplevel->toTraits(obj)); } } // Numeric property name. Except for float4, no fixed bindings have numeric names. // Keep in sync with related methods. See comments at Toplevel::getproperty. Atom MethodEnv::getpropertylate_u(Atom obj, uint32_t index) const { if (atomKind(obj) == 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); } #ifdef VMCFG_FLOAT else if (AvmCore::isFloat4(obj)) { if (index <= 3) { float4_t f4 = AvmCore::atomToFloat4(obj); float* pf4 = reinterpret_cast(&f4); return core()->floatToAtom(pf4[index]); } // See FIXME in Toplevel::getproperty() - do not absorb non-integer values outside // the range [0,2^32-2], but let them pass through to the prototype lookup. if (index != 4294967295U) toplevel()->throwRangeError(kOutOfRangeError, this->core()->internInt(index)); } #endif // *No* else clause { // Primitive types are not dynamic, so we can go directly // to their __proto__ object. AvmCore* core = this->core(); Toplevel *toplevel = this->toplevel(); ScriptObject *protoObject = toplevel->toPrototype(obj); return protoObject->ScriptObject::getStringPropertyFromProtoChain(core->internUint32(index), protoObject, toplevel->toTraits(obj)); } } // Numeric property name. Except for float4, no fixed bindings have numeric names. // Keep in sync with related methods. See comments at Toplevel::getproperty. Atom MethodEnv::getpropertylate_d(Atom obj, double index) const { if (atomKind(obj) == kObjectType) { // Use signed conversion, as it is faster on most platforms. int32_t index_i = int32_t(index); if (double(index_i) == index && index_i >= 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(uint32_t(index_i)); } else { // Negative, fractional, or out of range, so we must intern the double. // Sufficiently large non-negative numbers will also take this slower path, // but we know an exception will be thrown (Vector) or a dense representation // will not be used (Array, others). return AvmCore::atomToScriptObject(obj)->getAtomProperty(this->core()->internDouble(index)->atom()); } } #ifdef VMCFG_FLOAT else if (AvmCore::isFloat4(obj)) { float4_t f4 = AvmCore::atomToFloat4(obj); float* pf4 = reinterpret_cast(&f4); if (index == 0.0) return core()->floatToAtom(pf4[0]); if (index == 1.0) return core()->floatToAtom(pf4[1]); if (index == 2.0) return core()->floatToAtom(pf4[2]); if (index == 3.0) return core()->floatToAtom(pf4[3]); // See FIXME in Toplevel::getproperty() - do not absorb non-integer values outside // the range [0,2^32-2], but let them pass through to the prototype lookup. if (index >= 0.0 && index <= 4294967294.0) { uint32_t u = uint32_t(index); if (double(u) == index) toplevel()->throwRangeError(kOutOfRangeError, this->core()->internDouble(index)); } } #endif // *No* else clause { // Primitive types are not dynamic, so we can go directly // to their __proto__ object. AvmCore* core = this->core(); Toplevel *toplevel = this->toplevel(); ScriptObject *protoObject = toplevel->toPrototype(obj); return protoObject->ScriptObject::getStringPropertyFromProtoChain(core->internDouble(index), protoObject, toplevel->toTraits(obj)); } } ScriptObject* MethodEnv::finddef(const Multiname* multiname) const { // getting toplevel() is slightly more expensive than it used to be (more indirection)... // so only extract in the (rare) event of an exception AbcEnv* abcEnv = this->abcEnv(); AvmCore* core = abcEnv->core(); ScriptEnv* script = core->domainMgr()->findScriptEnvInAbcEnvByMultiname(abcEnv, *multiname); if (script == (ScriptEnv*)BIND_AMBIGUOUS) this->toplevel()->throwReferenceError(kAmbiguousBindingError, multiname); if (script == (ScriptEnv*)BIND_NONE) this->toplevel()->throwReferenceError(kUndefinedVarError, multiname); ScriptObject* global = script->global; if (!global) { global = script->initGlobal(); script->coerceEnter(global->atom()); } return global; } /*static*/ ScopeChain* ScriptEnv::createScriptScope(const ScopeTypeChain* stc, VTable* _vtable, AbcEnv* _abcEnv) { AvmCore* core = _vtable->core(); // NOTE the namespace here is the default xml namespaces. we use the caller's public return ScopeChain::create(core->GetGC(), _vtable, _abcEnv, stc, NULL, core->findPublicNamespace()); } ScriptObject* ScriptEnv::initGlobal() { AvmAssert(this->global == NULL); // object not defined yet. define it by running the script that exports it this->vtable()->resolveSignatures(this->scope()); // resolving the vtable also resolves the traits, if necessary ObjectClass* const objectClass = this->toplevel()->objectClass; ScriptObject* const delegate = objectClass ? objectClass->prototypePtr() : NULL; global = this->core()->newObject(this->vtable(), delegate); return global; } ScriptObject* MethodEnv::op_newobject(Atom* sp, int argc) const { // pre-size the hashtable since we know how many vars are coming Toplevel* toplevel = this->toplevel(); AvmCore* core = toplevel->core(); VTable* object_ivtable = toplevel->objectClass->ivtable(); ScriptObject* o = ScriptObject::create(core->GetGC(), object_ivtable, toplevel->objectClass->prototypePtr(), argc); for (; argc-- > 0; sp -= 2) { Atom name = sp[-1]; // verifier has stated this is a String, but we may see nullStringAtom if (!AvmCore::isString(name)) toplevel->throwTypeError(kConvertNullToObjectError); name = core->internString(name)->atom(); o->setAtomProperty(name, sp[0]); } return o; } Atom MethodEnv::nextname(Atom objAtom, int index) const { // Handle special case inputs; // bad index returns null, // null object throws error. if (index <= 0) { return nullStringAtom; } if (AvmCore::isNullOrUndefined(objAtom)) { toplevel()->throwTypeError( (objAtom == undefinedAtom) ? kConvertUndefinedToObjectError : kConvertNullToObjectError); } switch (atomKind(objAtom)) { case kObjectType: return AvmCore::atomToScriptObject(objAtom)->nextName(index); case kNamespaceType: return AvmCore::atomToNamespace(objAtom)->nextName(this->core(), index); #ifdef VMCFG_FLOAT case kSpecialBibopType: if (AvmCore::isFloat4(objAtom) && index <= 4) return atomFromIntptrValue(index-1); // Else fall through #endif // VMCFG_FLOAT 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 { // Handle special case inputs; // bad index returns undefined, // null object throws error. if (index <= 0) { return undefinedAtom; } if (AvmCore::isNullOrUndefined(objAtom)) { toplevel()->throwTypeError( (objAtom == undefinedAtom) ? kConvertUndefinedToObjectError : kConvertNullToObjectError); } switch (atomKind(objAtom)) { case kObjectType: return AvmCore::atomToScriptObject(objAtom)->nextValue(index); case kNamespaceType: return AvmCore::atomToNamespace(objAtom)->nextValue(index); case kSpecialBibopType: #ifdef VMCFG_FLOAT if (AvmCore::isFloat4(objAtom) && index <= 4) { float* f4p = (float*)atomPtr(objAtom); return toplevel()->core()->floatToAtom( f4p[index-1] ); } // Else fall through #endif 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 (atomKind(objAtom)) { case kObjectType: return AvmCore::atomToScriptObject(objAtom)->nextNameIndex(index); case kNamespaceType: return AvmCore::atomToNamespace(objAtom)->nextNameIndex(index); #ifdef VMCFG_FLOAT case kSpecialBibopType: if (AvmCore::isFloat4(objAtom) && index <= 3) return index + 1; // Else fall through #endif // VMCFG_FLOAT default: ScriptObject* proto = toplevel()->toPrototype(objAtom); int nextIndex = ( proto ? proto->nextNameIndex(index) : 0); return nextIndex; } } else { return 0; } } int32_t MethodEnv::hasnextproto(Atom& objAtom, int& index) const { if (index < 0) return 0; ScriptObject *delegate = NULL; if (!AvmCore::isNullOrUndefined(objAtom)) { switch (atomKind(objAtom)) { 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()->prototypePtr(); } break; #ifdef VMCFG_FLOAT case kSpecialBibopType: if (AvmCore::isFloat4(objAtom) && index <= 3) { index++; return 1; // I.e. "true"; but hasnextproto() traditionally returns int } // Else fall through #endif // VMCFG_FLOAT 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; } // see 13.2 creating function objects ClassClosure* MethodEnv::newfunction(MethodInfo *function, Atom* scopes) const { Toplevel* toplevel = this->toplevel(); AvmCore* core = toplevel->core(); FunctionClass* functionClass = toplevel->_functionClass; // can't use functionClass(), might not be inited yet VTable* fvtable = functionClass->ivtable(); VTable* object_ivtable = toplevel->objectClass->ivtable(); AvmAssert(fvtable->ivtable == NULL || fvtable->ivtable == object_ivtable); fvtable->ivtable = object_ivtable; AvmAssert(fvtable->linked); const ScopeTypeChain* fstc = function->declaringScope(); // functions should never have 'extra' scopes AvmAssert(fstc->size == fstc->fullsize); MMgc::GC* gc = core->GetGC(); ScopeChain* outer = scope(); ScopeChain* fscope = ScopeChain::create(gc, fvtable, abcEnv(), fstc, outer, core->dxns()); for (int i=outer->getSize(), n=fscope->getSize(); i < n; i++) { fscope->setScope(gc, i, *scopes++); } FunctionEnv* fenv = FunctionEnv::create(gc, function, fscope); FunctionObject* c = FunctionObject::create(gc, fvtable, fenv); c->setDelegate(functionClass->prototypePtr()); c->createVanillaPrototype(); c->prototypePtr()->setStringProperty(core->kconstructor, c->atom()); c->prototypePtr()->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(Traits* ctraits, ClassClosure *base, ScopeChain* outer, Atom* scopes) const { AvmCore* core = this->core(); // adds clarity to what is usually just global$init() SAMPLE_FRAME("[newclass]", core); MMgc::GC* gc = core->GetGC(); Toplevel* toplevel = this->toplevel(); Traits* itraits = ctraits->itraits; const BuiltinType bt = Traits::getBuiltinType(itraits); if (bt == BUILTIN_object) return toplevel->objectClass; if (bt == BUILTIN_class) return toplevel->_classClass; AbcEnv* abcEnv = this->abcEnv(); // finish resolving the base class if (!base && itraits->base) { // class has a base but no base object was provided GCRef error = toplevel->typeErrorClass(); if (error) error->throwError(kConvertNullToObjectError); else toplevel->throwTypeError(kCorruptABCError); } // have to use local variables for CodeWarrior Traits* baseIvtableTraits = base ? base->ivtable()->traits : 0; Traits* itraitsBase = itraits->base; // make sure the traits of the base vtable matches the base traits if (!((base == NULL && itraits->base == NULL) || (base != NULL && itraitsBase == baseIvtableTraits))) { GCRef error = toplevel->verifyErrorClass(); if (error) error->throwError(kInvalidBaseClassError); else toplevel->throwTypeError(kCorruptABCError); } VTable* ivtable = core->newVTable(itraits, base ? base->ivtable() : NULL, toplevel); VTable* class_ivtable = toplevel->_classClass->ivtable(); VTable* cvtable = core->newVTable(ctraits, class_ivtable, toplevel); // class scopechain = [..., class] AvmAssert(ctraits->init != NULL); ScopeChain* cscope = ScopeChain::create(gc, cvtable, abcEnv, ctraits->init->declaringScope(), outer, core->dxns()); int i = outer->getSize(); for (int n=cscope->getSize(); i < n; i++) { cscope->setScope(gc, i, *scopes++); } AvmAssert(itraits->init != NULL); ScopeChain* iscope = ScopeChain::create(gc, ivtable, abcEnv, itraits->init->declaringScope(), cscope, core->dxns()); ivtable->resolveSignatures(iscope); cvtable->ivtable = ivtable; cvtable->resolveSignatures(cscope); CreateClassClosureProc createClassClosure = cvtable->traits->getCreateClassClosureProc(); AvmAssert(createClassClosure != NULL); ClassClosure* cc = (*createClassClosure)(cvtable); if (cc->prototypePtr()) { // C.prototype.__proto__ = Base.prototype if (base != NULL) cc->prototypePtr()->setDelegate(base->prototypePtr()); // C.prototype.constructor = C {DontEnum} cc->initPrototypeConstructor(); } AvmAssert(i == iscope->getSize()-1); iscope->setScope(gc, i, cc->atom()); AvmAssert(toplevel->_classClass != NULL); cc->setDelegate(toplevel->_classClass->prototypePtr()); cvtable->init->coerceEnter(cc->atom()); // ignore result return cc; } void MethodEnv::initproperty(Atom obj, const Multiname* multiname, Atom value, VTable* vtable) const { Toplevel* toplevel = this->toplevel(); Traitsp declarer = NULL; Binding b = toplevel->getBindingAndDeclarer(vtable->traits, *multiname, declarer); if (AvmCore::bindingKind(b) == BKIND_CONST) { if (this->method != declarer->init) toplevel->throwReferenceError(kConstWriteError, multiname, vtable->traits); b = AvmCore::makeSlotBinding(AvmCore::bindingToSlotId(b), BKIND_VAR); } toplevel->setproperty_b(obj, multiname, value, vtable, b); } void MethodEnv::setpropertylate_i(Atom obj, int32_t 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(this->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()->getPublicNamespace(method->pool()), core()->internInt(index)); toplevel()->throwReferenceError(kWriteSealedError, &tempname, toplevel()->toTraits(obj)); } } void MethodEnv::setpropertylate_u(Atom obj, uint32_t 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()->getPublicNamespace(method->pool()), core()->internUint32(index)); toplevel()->throwReferenceError(kWriteSealedError, &tempname, toplevel()->toTraits(obj)); } } void MethodEnv::setpropertylate_d(Atom obj, double index, Atom value) const { if (AvmCore::isObject(obj)) { ScriptObject* o = AvmCore::atomToScriptObject(obj); // Use signed conversion, as it is faster on most platforms. // We do not allocate objects large enough for an integral value // that will not fit an int32 to index a vector or dense array, // thus we'd have to use a string property name anyway in the // cases where this conversion fails. int32_t index_i = int32_t(index); if (double(index_i) == index && index_i >= 0) { o->setUintProperty(uint32_t(index_i), value); return; } // On the face of it, it should be sufficient to fall back to using // setAtomProperty() here. Unfortunately, due to former differences // in the way that setUintProperty() and setAtomProperty() handled // out-of-range indices, a RangeError might have been thrown where // setAtomProperty() would have thrown a ReferenceError. We have to // preserve the distinct code paths so that the SWF10 compatibility // logic can distinguish these cases. // Try unsigned conversion. uint32_t index_u = uint32_t(index); if (double(index_u) == index) { o->setUintProperty(index_u, value); return; } // Negative, fractional, or out of range, so we must intern the double. // Sufficiently large non-negative numbers will also take this slower path, // but we know an exception will be thrown (Vector) or a dense representation // will not be used (Array, others). o->setAtomProperty(this->core()->internDouble(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()->getPublicNamespace(method->pool()), core()->internDouble(index)); toplevel()->throwReferenceError(kWriteSealedError, &tempname, toplevel()->toTraits(obj)); } } #ifdef VMCFG_FLOAT void MethodEnv::setpropertylate_f(Atom obj, float index_f, Atom value) const { if (AvmCore::isObject(obj)) { ScriptObject* o = AvmCore::atomToScriptObject(obj); // Use signed conversion, as it is faster on most platforms. // We do not allocate objects large enough for an integral value // that will not fit an int32 to index a vector or dense array, // thus we'd have to use a string property name anyway in the // cases where this conversion fails. int32_t index_i = int32_t(index_f); if (float(index_i) == index_f && index_i >= 0) { o->setUintProperty(uint32_t(index_i), value); return; } // No need to worry about SWF10 compatibility (like setpropertylate_d does) // Negative, fractional, or out of range, so we must intern the float. // Sufficiently large non-negative numbers will also take this slower path, // but we know an exception will be thrown (Vector) or a dense representation // will not be used (Array, others). o->setAtomProperty(this->core()->internFloat(index_f)->atom(), value); } else { // obj represents a primitive Number, Boolean, int, float 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()->getPublicNamespace(method->pool()), core()->internFloat(index_f)); toplevel()->throwReferenceError(kWriteSealedError, &tempname, toplevel()->toTraits(obj)); } } #endif Atom MethodEnv::callsuper(const Multiname* multiname, int argc, Atom* atomv) const { VTable* base = this->vtable()->base; Toplevel* toplevel = this->toplevel(); Binding b = toplevel->getBinding(base->traits, multiname); switch (AvmCore::bindingKind(b)) { default: toplevel->throwReferenceError(kCallNotFoundError, multiname, base->traits); case BKIND_METHOD: { MethodEnv* superenv = base->methods[AvmCore::bindingToMethodId(b)]; return superenv->coerceEnter(argc, atomv); } case BKIND_VAR: case BKIND_CONST: { uint32_t slot = AvmCore::bindingToSlotId(b); ScriptObject* function = AvmCore::atomToScriptObject(atomv[0])->getSlotObject(slot); return op_call(this, function, argc, atomv); } case BKIND_SET: { // read on write-only property toplevel->throwReferenceError(kWriteOnlyError, multiname, base->traits); } case BKIND_GET: case BKIND_GETSET: { // Invoke the getter int m = AvmCore::bindingToGetterId(b); MethodEnv *f = base->methods[m]; Atom method = f->coerceEnter(atomv[0]); return avmplus::op_call(this, method, argc, atomv); } } } Atom MethodEnv::delproperty(Atom obj, const 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 (AvmCore::isXMLorXMLList(obj) && multiname->containsAnyPublicNamespace()) { // dynamic props should hide declared methods on delete ScriptObject* so = AvmCore::atomToScriptObject(obj); bool b = so->deleteMultinameProperty(multiname); return b ? trueAtom : falseAtom; } } } #ifdef VMCFG_FLOAT else if (AvmCore::isFloat4(obj)) { // See FIXME in Toplevel::getproperty() for an explanation of this logic. if (multiname->isValidDynamicName()) { uint32_t index; if (AvmCore::getIndexFromAtom(multiname->getName()->atom(), &index)) { if (index <= 3) return falseAtom; return trueAtom; } } toplevel->throwReferenceError(kDeleteSealedError, multiname, traits); } #endif else { toplevel->throwReferenceError(kDeleteSealedError, multiname, traits); } return falseAtom; } Atom MethodEnv::getsuper(Atom obj, const Multiname* multiname) const { VTable* vtable = this->vtable()->base; Toplevel* toplevel = this->toplevel(); Binding b = toplevel->getBinding(vtable->traits, multiname); switch (AvmCore::bindingKind(b)) { default: toplevel->throwReferenceError(kReadSealedError, multiname, vtable->traits); case BKIND_METHOD: { // extracting a virtual method MethodEnv *m = vtable->methods[AvmCore::bindingToMethodId(b)]; return toplevel->methodClosureClass()->create(m, obj)->atom(); } case BKIND_VAR: case BKIND_CONST: return AvmCore::atomToScriptObject(obj)->getSlotAtom(AvmCore::bindingToSlotId(b)); case BKIND_SET: { // read on write-only property toplevel->throwReferenceError(kWriteOnlyError, multiname, vtable->traits); } case BKIND_GET: case BKIND_GETSET: { // Invoke the getter int m = AvmCore::bindingToGetterId(b); MethodEnv *f = vtable->methods[m]; return f->coerceEnter(obj); } } } void MethodEnv::setsuper(Atom obj, const Multiname* multiname, Atom value) const { VTable* vtable = this->vtable()->base; Toplevel* toplevel = this->toplevel(); Binding b = toplevel->getBinding(vtable->traits, multiname); switch (AvmCore::bindingKind(b)) { default: toplevel->throwReferenceError(kWriteSealedError, multiname, vtable->traits); case BKIND_CONST: toplevel->throwReferenceError(kConstWriteError, multiname, vtable->traits); case BKIND_METHOD: toplevel->throwReferenceError(kCannotAssignToMethodError, multiname, vtable->traits); case BKIND_GET: toplevel->throwReferenceError(kConstWriteError, multiname, vtable->traits); case BKIND_VAR: // @todo, this does a redundant coercion -- no harm done, just slightly more work than necessary AvmCore::atomToScriptObject(obj)->coerceAndSetSlotAtom(AvmCore::bindingToSlotId(b), value); return; case BKIND_SET: case BKIND_GETSET: { // Invoke the setter uint32_t m = AvmCore::bindingToSetterId(b); AvmAssert(m < vtable->traits->getTraitsBindings()->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, const Multiname* multiname) { Toplevel* toplevel = this->toplevel(); if (atomKind(atom)==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, const 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; } const 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->getScopeTraitsAt(outerTraits->size); } 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->getScopeIsWithAt(i)) { 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->getScopeTraitsAt(i); 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 { Atom global = outer->getSize() > 0 ? outer->getScope(0) : *scopes; Atom obj = findglobalproperty(global, multiname); if (AvmCore::isNullOrUndefined(obj)) { if (strict) toplevel->throwReferenceError(kUndefinedVarError, multiname); obj = global; } return obj; } } Atom MethodEnv::findglobalproperty(Atom target_global, const Multiname* multiname) { // wildcards used here always fail later on, so just check now. if (multiname->isAnyName() || multiname->isAnyNamespace()) return nullObjectAtom; Toplevel* toplevel = this->toplevel(); // look for imported definition (similar logic to OP_finddef). This will // find definitions in this script and in other scripts. AbcEnv* abcEnv = this->abcEnv(); AvmCore* core = abcEnv->core(); ScriptEnv* script = core->domainMgr()->findScriptEnvInAbcEnvByMultiname(abcEnv, *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(); script->coerceEnter(script->global->atom()); } return global->atom(); } // no imported definition found. look for dynamic props // on the global object // 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 = AvmCore::isObject(target_global) ? AvmCore::atomToScriptObject(target_global) : toplevel->toPrototype(target_global); do { if (o->hasMultinameProperty(multiname)) return target_global; } while ((o = o->getDelegate()) != NULL); return nullObjectAtom; } Namespace* MethodEnv::internRtns(Atom nsAtom) { if ((atomKind(nsAtom) != 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->needClosure()) { 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, const 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 } } void MethodEnv::checkfilter(Atom obj) { if ( !AvmCore::isXMLorXMLList(obj) ) { Toplevel* toplevel = this->toplevel(); toplevel->throwTypeError(kFilterError, core()->toErrorString(toplevel->toTraits(obj))); } } VTable* MethodEnv::buildActivationVTable() { const ScopeTypeChain* activationScopeTraits = method->activationScope(); Toplevel* toplevel = this->toplevel(); // This can happen when the ABC has MethodInfo data but not MethodBody data if (!activationScopeTraits) { toplevel->throwVerifyError(kCorruptABCError); } Traits* activationTraits = activationScopeTraits->traits(); AvmAssert(activationTraits != NULL); AvmCore* core = this->core(); // Bugzilla 671413 (note below is from Steven Johnson): // This is quite possibly the ugliest hack I've ever inserted into Tamarin. // The problem: at the ABC level, activation objects can be coerced into plain Objects, // but they aren't plain Objects (they are distinct types). If a call to one of Object's // methods (e.g. AS3::hasOwnProperty) is early bound to this, we'll attempt to call // a method using a non-existent MethodEnv on the activation object's VTable -- crashamundo. // The *proper* fix is (arguably) to smarten Verifier to ensure that activation objects // can't be coerced, or leaked into a different structure/function that might coerce them, // but that's tricky to do and a Quick Fix is needed for Wasabi. Hence, the pragmatic approach: // artificially graft the MethodEnvs from Object onto each activation VTable, so that if // such a dubious call happens, it quietly calls the proper Object method. (Arguably, a better // approach would be to call a method that throws a VerifyError, but since this is intended // as a temporary fix for fuzzed/malformed ABC in the first place, I don't think it's worth // the extra effort.) MMgc::GC* gc = core->GetGC(); VTable* object_ivtable = toplevel->objectClass->ivtable(); Traits* object_itraits = object_ivtable->traits; uint32_t const object_method_count = object_itraits->getTraitsBindings()->methodCount; AvmAssert(object_method_count > 0); // activations shouldn't ever have methods. uint32_t const activation_method_count = activationTraits->getTraitsBindings()->methodCount; AvmAssert(activation_method_count == 0); if (activation_method_count > 0) toplevel->throwVerifyError(kCorruptABCError); size_t const extraSize = sizeof(MethodEnv*) * (object_method_count-1); VTable* activation = new (gc, MMgc::kExact, extraSize) VTable(activationTraits, NULL, toplevel); ScopeChain* activationScope = this->scope()->cloneWithNewVTable(core->GetGC(), activation, this->abcEnv(), activationScopeTraits); activation->resolveSignatures(activationScope); // Bugzilla 671413 for (uint32_t i = 0; i < object_method_count; i++) WB(gc, activation, &activation->methods[i], object_ivtable->methods[i]); return activation; } VTable* MethodEnv::getActivationVTable() { if (!method->needActivation()) return NULL; VTable* activation; const int type = getType(); if (!activationOrMCTable) { activation = buildActivationVTable(); setActivationOrMCTable(activation, kActivation); } else if (type == kMethodTable) { activation = buildActivationVTable(); ActivationMethodTablePair *amtp = new (core()->GetGC()) ActivationMethodTablePair(activation, getMethodClosureTable()); setActivationOrMCTable(amtp, kActivationMethodTablePair); } else if (type == kActivationMethodTablePair) { activation = getPair()->activation; } else { activation = (VTable*)(activationOrMCTable&~7); } AvmAssert(activation != NULL); return activation; } ScriptObject *MethodEnv::newUninitializedActivation() { VTable *vtable = getActivationVTable(); AvmCore *core = this->core(); MMgc::GC *gc = core->GetGC(); SAMPLE_FRAME("[activation-object]", core); ScriptObject* obj = ScriptObject::create(gc, vtable, 0/*delegate*/); AvmAssert(obj->getDelegate() == NULL); // activation objects must not have a prototype pointer. return obj; } ScriptObject *MethodEnv::newActivation() { ScriptObject* obj = newUninitializedActivation(); MethodEnv *init = obj->vtable->init; if (init) init->coerceEnter(obj->atom()); AvmAssert(obj->getDelegate() == NULL); // activation objects must not have a prototype pointer. return obj; } WeakKeyHashtable *MethodEnv::getMethodClosureTable() { int type = getType(); if(!activationOrMCTable) { WeakKeyHashtable *wkh = WeakKeyHashtable::create(core()->GetGC()); setActivationOrMCTable(wkh, kMethodTable); return wkh; } else if(type == kActivation) { WeakKeyHashtable *wkh = WeakKeyHashtable::create(core()->GetGC()); ActivationMethodTablePair *amtp = new (core()->GetGC()) ActivationMethodTablePair(getActivationVTable(), wkh); setActivationOrMCTable(amtp, kActivationMethodTablePair); return wkh; } else if(type == kActivationMethodTablePair) { return getPair()->methodTable; } return (WeakKeyHashtable*)(activationOrMCTable&~7); } #ifdef _DEBUG void FASTCALL check_unbox(MethodEnv* env, bool u) { AvmAssert((env->method->unboxThis() != 0) == u); } #endif #ifdef DEBUGGER ArrayObject* MethodEnv::getLexicalScopes() { if(_scope->getSize() == 0) return NULL; ArrayObject *scopes = toplevel()->arrayClass()->newArray(_scope->getSize()); for(int i=_scope->getSize()-1, j=0; i >= 0; i--, j++) { Atom scope = _scope->getScope(i); scopes->setUintProperty(j, scope); } return scopes; } #endif }