/* -*- 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 { using namespace MMgc; // ------------------------------------------------------------------- // ------------------------------------------------------------------- // ------------------------------------------------------------------- // ------------------------------------------------------------------- /*static*/ TraitsBindings* TraitsBindings::alloc(GC* gc, Traits* _owner, TraitsBindingsp _base, MultinameBindingHashtable* _bindings, uint32_t slotCount, uint32_t methodCount, bool typesValid) { const uint32_t extra = typesValid ? slotCount * sizeof(SlotInfo) + methodCount * sizeof(MethodInfo) : 0; TraitsBindings* tb = new (gc, MMgc::kExact, extra) TraitsBindings(_owner, _base, _bindings, slotCount, methodCount, typesValid); if (_base && typesValid) { if (_base->slotCount) { const SlotInfo* src = &_base->getSlots()[0]; SlotInfo* dst = &tb->getSlots()[0]; VMPI_memcpy(dst, src, _base->slotCount * sizeof(SlotInfo)); AvmAssert(((_owner->isMachineType()) || (tb->owner->m_sizeofInstance >= _base->owner->m_sizeofInstance))); } if (_base->methodCount) VMPI_memcpy(&tb->getMethods()[0], &_base->getMethods()[0], _base->methodCount * sizeof(MethodInfo)); } return tb; } void TraitsBindings::gcTraceHook_TraitsBindings(MMgc::GC* gc) { if (m_typesValid) { SlotInfo* slots = getSlots(); for ( uint32_t i=0 ; i < slotCount ; i++ ) gc->TraceLocation(&slots[i].type); TraitsBindings::BindingMethodInfo* methods = getMethods(); for ( uint32_t i=0 ; i < methodCount ; i++ ) gc->TraceLocation(&methods[i].f); } } Binding TraitsBindings::findBinding(Stringp name) const { for (TraitsBindingsp self = this; self; self = self->base) { const Binding b = self->m_bindings->getName(name); if (b != BIND_NONE) return b; } return BIND_NONE; } Binding TraitsBindings::findBinding(Stringp name, Namespacep ns) const { for (TraitsBindingsp self = this; self; self = self->base) { const Binding b = self->m_bindings->get(name, ns); if (b != BIND_NONE) return b; } return BIND_NONE; } Binding TraitsBindings::findBinding(Stringp name, NamespaceSetp nsset) const { for (TraitsBindingsp self = this; self; self = self->base) { const Binding b = self->m_bindings->get(name, nsset); if (b != BIND_NONE) return b; } return BIND_NONE; } Binding TraitsBindings::findBindingAndDeclarer(const Multiname& mn, Traitsp& declarer) const { if (mn.isBinding()) { for (TraitsBindingsp self = this; self; self = self->base) { Namespacep foundns = NULL; Binding const b = self->m_bindings->getMulti(mn, foundns); if (b != BIND_NONE) { declarer = self->owner; // if the member is 'protected' then we have to do extra work, // as we may have found it in a descendant's protected namespace -- // we have to bounce up the inheritance chain and check our parent's // protected namespace. while (foundns == declarer->protectedNamespace) { Traitsp declParent = declarer->base; if (!declParent || declParent->protectedNamespace == NULL) break; Binding const bp = declParent->getTraitsBindings()->findBinding(mn.getName(), declParent->protectedNamespace); if (bp != b) break; // self->owner->core->console<<"bounce "<protectedNamespace; } // self->owner->core->console<<"final declarer is "< 0) { const uint32_t sizeofInstance = this->owner->getSizeOfInstance(); const TraitsBindings::SlotInfo* tbs = getSlots() + (slotCount - slotAreaCount); const TraitsBindings::SlotInfo* tbs_end = tbs + slotAreaCount; for ( ; tbs < tbs_end; ++tbs) { // offset is pointed off the end of our object if (isAtomOrRCObjectSlot(tbs->sst())) { //owner->core->console<<"SDI "<type<<" "<offset()<<"\n"; AvmAssert(tbs->offset() >= sizeofInstance); const uint32_t off = tbs->offset() - sizeofInstance; AvmAssert((off % 4) == 0); // if slot is "big" then this is the bit of the first 4 bytes. that's fine. slotDestroyInfo.set((off>>2)+1); // +1 to leave room for bit 0 slotDestroyInfo.set(0); // bit 0 is "anyset" flag } // otherwise leave the bit zero } } else { AvmAssert(slotAreaCount == 0); AvmAssert(bitsNeeded == 0); } // if nothing set, blow away what we built and realloc as single clear bit -- smaller and faster if (!slotDestroyInfo.test(0)) { slotDestroyInfo.resize(gc, 1); AvmAssert(!slotDestroyInfo.test(0)); } } bool TraitsBindings::checkOverride(AvmCore* core, MethodInfo* virt, MethodInfo* over) const { if (over == virt) return true; MethodSignaturep overms = over->getMethodSignature(); MethodSignaturep virtms = virt->getMethodSignature(); Traits* overTraits = overms->returnTraits(); Traits* virtTraits = virtms->returnTraits(); if (overTraits != virtTraits) { #ifdef AVMPLUS_VERBOSE core->console << "\n"; core->console << "return types dont match\n"; core->console << " virt " << virtTraits << " " << virt << "\n"; core->console << " over " << overTraits << " " << over << "\n"; #endif return false; } if (overms->param_count() != virtms->param_count() || overms->optional_count() != virtms->optional_count()) { #ifdef AVMPLUS_VERBOSE core->console << "\n"; core->console << "param count mismatch\n"; core->console << " virt params=" << virtms->param_count() << " optional=" << virtms->optional_count() << " " << virt << "\n"; core->console << " over params=" << overms->param_count() << " optional=" << overms->optional_count() << " " << virt << "\n"; #endif return false; } // allow subclass param 0 to implement or extend base param 0 virtTraits = virtms->paramTraits(0); if (!owner->subtypeof(virtTraits) || !Traits::isMachineCompatible(this->owner, virtTraits)) { if (!this->owner->isMachineType() && virtTraits == core->traits.object_itraits) { over->setUnboxThis(); } else { #ifdef AVMPLUS_VERBOSE core->console << "\n"; core->console << "param 0 incompatible\n"; core->console << " virt " << virtTraits << " " << virt << "\n"; core->console << " over " << this->owner << " " << over << "\n"; #endif return false; } } for (int k=1, p=overms->param_count(); k <= p; k++) { overTraits = overms->paramTraits(k); virtTraits = virtms->paramTraits(k); if (overTraits != virtTraits) { #ifdef AVMPLUS_VERBOSE core->console << "\n"; core->console << "param " << k << " incompatible\n"; core->console << " virt " << virtTraits << " " << virt << "\n"; core->console << " over " << overTraits << " " << over << "\n"; #endif return false; } } if (virt->unboxThis()) { // the _unboxThis flag is sticky, all the way down the inheritance tree over->setUnboxThis(); } return true; } static bool isCompatibleOverrideKind(BindingKind baseKind, BindingKind overKind) { static const uint8_t kCompatibleBindingKinds[8] = { 0, // BKIND_NONE (1<getTraitsBindings(); StTraitsBindingsIterator iter(ifcd); while (iter.next()) { Stringp name = iter.key(); if (!name) continue; Namespacep ns = iter.ns(); Binding iBinding = iter.value(); const BindingKind iBindingKind = AvmCore::bindingKind(iBinding); Binding cBinding = this->findBinding(name, ns); // The second lookup that we used to do here is unnecessary. The name would // have been brought out for this namespace by fixOneInterfaceBindings(). // We used to do something like: // // if (iBinding == cBinding) continue; // // here, presumably on the theory that if the bindings are the same, we don't // need to check any further. THIS IS WRONG: we must check the actual argument // types in all cases. if (!isCompatibleOverrideKind(iBindingKind, AvmCore::bindingKind(cBinding))) return false; switch (iBindingKind) { default: { AvmAssert(0); // interfaces shouldn't have anything but methods, getters, and setters return false; } case BKIND_METHOD: { MethodInfo* virt = ifcd->getMethods()[AvmCore::bindingToMethodId(iBinding)].f; MethodInfo* over = this->getMethods()[AvmCore::bindingToMethodId(cBinding)].f; if (!checkOverride(core, virt, over)) return false; break; } case BKIND_GET: case BKIND_SET: case BKIND_GETSET: { // check getter & setter overrides if (AvmCore::hasGetterBinding(iBinding)) { if (!AvmCore::hasGetterBinding(cBinding)) return false; MethodInfo* virt = ifcd->getMethods()[AvmCore::bindingToGetterId(iBinding)].f; MethodInfo* over = this->getMethods()[AvmCore::bindingToGetterId(cBinding)].f; if (!checkOverride(core, virt, over)) return false; } if (AvmCore::hasSetterBinding(iBinding)) { if (!AvmCore::hasSetterBinding(cBinding)) return false; MethodInfo* virt = ifcd->getMethod(AvmCore::bindingToSetterId(iBinding)); MethodInfo* over = this->getMethod(AvmCore::bindingToSetterId(cBinding)); if (!checkOverride(core, virt, over)) return false; } } break; } // switch } // for j } // for tbi return true; } void TraitsBindings::fixOneInterfaceBindings(Traitsp ifc) { AvmAssert(!owner->isInterface()); TraitsBindingsp ifcd = ifc->getTraitsBindings(); StTraitsBindingsIterator iter(ifcd); while (iter.next()) { Stringp name = iter.key(); if (!name) continue; Namespacep ns = iter.ns(); Binding iBinding = iter.value(); const BindingKind iBindingKind = AvmCore::bindingKind(iBinding); const Binding cBinding = this->findBinding(name, ns); if (!isCompatibleOverrideKind(iBindingKind, AvmCore::bindingKind(cBinding))) { // Try again with public namespace that matches the version of the current traits const Binding pBinding = this->findBinding(name, ifc->core->getPublicNamespace(owner->pool)); if (isCompatibleOverrideKind(iBindingKind, AvmCore::bindingKind(pBinding))) { this->m_bindings->add(name, ns, pBinding); } } } } void Traits::addVersionedBindings(MultinameBindingHashtable* bindings, Stringp name, NamespaceSetp nss, Binding binding) const { // find the lowest version within the active series. ApiVersion apiVersion = kApiVersion_VM_INTERNAL; for (NamespaceSetIterator iter(nss); iter.hasNext();) { Namespacep ns = iter.next(); ApiVersion a = core->getValidApiVersion(ns->getApiVersion()); if (a < apiVersion) apiVersion = a; } Namespacep ns = core->getVersionedNamespace(nss->nsAt(0), apiVersion); bindings->add(name, ns, binding); } // ------------------------------------------------------------------- // ------------------------------------------------------------------- TraitsMetadata::MetadataPtr TraitsMetadata::getSlotMetadataPos(uint32_t i, PoolObject*& residingPool) const { AvmAssert(i < slotCount); residingPool = NULL; for (TraitsMetadatap self = this; self && (i < self->slotCount); self = self->base) { MetadataPtr pos = self->slotMetadataPos[i]; if (pos) { residingPool = self->residingPool; return pos; } } return NULL; } TraitsMetadata::MetadataPtr TraitsMetadata::getMethodMetadataPos(uint32_t i, PoolObject*& residingPool) const { AvmAssert(i < methodCount); residingPool = NULL; for (TraitsMetadatap self = this; self && (i < self->methodCount); self = self->base) { MetadataPtr pos = self->methodMetadataPos[i]; if (pos) { residingPool = self->residingPool; return pos; } } return NULL; } // ------------------------------------------------------------------- // ------------------------------------------------------------------- Traits::Traits(PoolObject* _pool, Traits* _base, uint16_t _sizeofInstance, uint16_t _offsetofSlots, TraitsPosPtr traitsPos, TraitsPosType posType) : core(_pool->core) , base(_base) , m_paramTraits(NULL) , pool(_pool) , m_traitsPos(traitsPos) , m_tbref(_pool->core->GetGC()->emptyWeakRef) , m_tmref(_pool->core->GetGC()->emptyWeakRef) , m_sizeofInstance(_sizeofInstance) , m_offsetofSlots(_offsetofSlots) , builtinType(BUILTIN_none) , m_posType(uint8_t(posType)) // Assume builtins initialize this correctly. This is verified // by checkCustomConstruct() in MethodEnv.cpp. , hasCustomConstruct(0) #ifdef VMCFG_AOT , m_interfaceBindingFunction(NULL) #endif { AvmAssert(m_tbref->isNull()); AvmAssert(m_tmref->isNull()); AvmAssert(BUILTIN_COUNT <= 32); AvmAssert(m_slotDestroyInfo.allocatedSize() == 0); #ifdef _DEBUG switch (posType) { case TRAITSTYPE_NVA: case TRAITSTYPE_RT: AvmAssert(m_traitsPos == 0); break; default: AvmAssert(m_traitsPos != 0); break; } #endif build_primary_supertypes(); build_secondary_supertypes(); } /*static*/ Traits* Traits::newTraits(PoolObject* pool, Traits *base, uint16_t objectSize, uint16_t offsetOfSlots, TraitsPosPtr traitsPos, TraitsPosType posType) { AvmAssert(posType != TRAITSTYPE_CATCH); AvmAssert(pool != NULL); Traits* traits = new (pool->core->GetGC(), MMgc::kExact) Traits(pool, base, objectSize, offsetOfSlots, traitsPos, posType); return traits; } /*static*/ Traits* Traits::newCatchTraits(const Toplevel* toplevel, PoolObject* pool, TraitsPosPtr traitsPos, Stringp name, Namespacep ns) { AvmAssert(pool != NULL); Traits* traits = new (pool->core->GetGC(), MMgc::kExact) Traits(pool, NULL, sizeof(ScriptObject), sizeof(ScriptObject), traitsPos, TRAITSTYPE_CATCH); traits->final = true; traits->set_names(ns, name); traits->verifyBindings(toplevel); traits->resolveSignatures(toplevel); return traits; } Traits* Traits::_newParameterizedTraits(Stringp name, Namespacep ns, Traits* _base, Traits* _param_traits) { Traits* newtraits = Traits::newTraits(this->pool, _base, this->getSizeOfInstance(), this->m_offsetofSlots, NULL, TRAITSTYPE_RT); newtraits->m_needsHashtable = this->m_needsHashtable; newtraits->m_paramTraits = _param_traits; newtraits->set_names(ns, name); return newtraits; } TraitsPosPtr Traits::traitsPosStart() const { TraitsPosPtr pos = m_traitsPos; switch (posType()) { case TRAITSTYPE_INTERFACE: case TRAITSTYPE_INSTANCE: pos = skipToInstanceInitPos(pos); // fall thru, no break case TRAITSTYPE_CLASS: case TRAITSTYPE_SCRIPT: AvmCore::skipU32(pos, 1); // skip in init_index break; case TRAITSTYPE_ACTIVATION: // nothing to do break; case TRAITSTYPE_CATCH: case TRAITSTYPE_NVA: case TRAITSTYPE_RT: pos = NULL; break; } return pos; } TraitsPosPtr Traits::skipToInstanceInitPos(TraitsPosPtr pos) const { AvmAssert(isInstanceType() && pos != NULL); AvmCore::skipU32(pos, 2); // skip the QName & base traits const uint8_t theflags = *pos++; const bool hasProtected = (theflags & 8) != 0; if (hasProtected) { AvmCore::skipU32(pos); } const uint32_t interfaceCount = AvmCore::readU32(pos); AvmCore::skipU32(pos, interfaceCount); return pos; } #ifdef VMCFG_FLOAT static bool is16ByteSlot(Traits* slotTE) { return Traits::getBuiltinType(slotTE) == BUILTIN_float4; } #endif // VMCFG_FLOAT static bool is8ByteSlot(Traits* slotTE) { #ifdef AVMPLUS_64BIT const uint32_t BIG_TYPE_MASK = (~(NUMERIC_TYPE_MASK | (1U<base ? this->base->getTraitsBindings() : NULL; while (tb != NULL && tb->slotCount > 0) { if (tb->owner->pool != this->pool && tb->slotCount > 0) { return false; } tb = tb->base; } return true; } void Traits::buildBindings(TraitsBindingsp basetb, MultinameBindingHashtable* bindings, uint32_t& slotCount, uint32_t& methodCount, SlotSizeInfo* slotSizeInfo, const Toplevel* toplevel) const { const uint8_t* pos = traitsPosStart(); const uint32_t baseSlotCount = basetb ? basetb->slotCount : 0; const uint32_t baseMethodCount = basetb ? basetb->methodCount : 0; //slotCount = baseSlotCount; methodCount = baseMethodCount; SlotIdCalcer sic(baseSlotCount, this->allowEarlyBinding()); #ifdef _DEBUG uint32_t pointerSlots = 0; #endif NameEntry ne; const uint32_t nameCount = pos ? AvmCore::readU32(pos) : 0; for (uint32_t i = 0; i < nameCount; i++) { ne.readNameEntry(pos); Multiname mn; this->pool->resolveBindingNameNoCheck(ne.qni, mn, toplevel); Stringp name = mn.getName(); Namespacep ns; NamespaceSetp compat_nss; if (mn.namespaceCount() > 1) { ns = mn.getNsset()->nsAt(0); compat_nss = mn.getNsset(); } else { ns = mn.getNamespace(); compat_nss = NamespaceSet::create(core->GetGC(), ns); } switch (ne.kind) { case TRAIT_Slot: case TRAIT_Const: case TRAIT_Class: { uint32_t slot_id = sic.calc_id(ne.id); if (toplevel) { AvmAssert(!this->m_resolved); // when we resolve, we must do some additional verification checks... these were formerly // done in AbcParser::parseTraits but require the base class to be resolved first, so we // now defer it to here. // illegal raw slot id -- reject IF we are early binding. (theoretically it's always illegal, // but won't cause any problems if earlySlotBinding=false, and rejecting in this case // will break existing content.) if (ne.id > nameCount && sic.earlySlotBinding()) toplevel->throwVerifyError(kCorruptABCError); // slots are final. if (basetb && slot_id < basetb->slotCount) toplevel->throwVerifyError(kIllegalOverrideError, core->toErrorString(mn), core->toErrorString(base)); // a slot cannot override anything else. if (bindings->get(name, ns) != BIND_NONE) toplevel->throwVerifyError(kCorruptABCError); // In theory we should reject duplicate slots here; // in practice we don't, as it causes problems with some existing content //if (basetb->findBinding(name, ns) != BIND_NONE) // toplevel->throwVerifyError(kIllegalOverrideError, toplevel->core()->toErrorString(qn), toplevel->core()->toErrorString(this)); // Interfaces cannot have slots. if (this->isInterface()) toplevel->throwVerifyError(kIllegalSlotError, core->toErrorString(this)); } // these assertions are commented out because the new lazy-init code can // fire them inapppriately -- they all indicate malformed ABC, but they // *can* occur on a pre-resolve build of TraitsBindings. We'll just let // them slide by since we check for them all at resolve time. // AvmAssert(!(ne.id > nameCount)); // unhandled verify error // AvmAssert(!(basetb && slot_id < basetb->slotCount)); // unhandled verify error // AvmAssert(!(bindings->get(name, ns) != BIND_NONE)); // unhandled verify error addVersionedBindings(bindings, name, compat_nss, AvmCore::makeSlotBinding(slot_id, ne.kind==TRAIT_Slot ? BKIND_VAR : BKIND_CONST)); if (slotSizeInfo != NULL) { // only do this if we need it -- doing it too early could cause us to reference a not-yet-loaded // type which we don't need yet, causing an unnecessary verification failure Traitsp slotType = (ne.kind == TRAIT_Class) ? pool->getClassTraits(ne.info) : pool->resolveTypeName(ne.info, toplevel); if (!isPointerSlot(slotType)) { #ifdef VMCFG_FLOAT if (is16ByteSlot(slotType)) slotSizeInfo->nonPointer128BitSlotCount += 1; else #endif // VMCFG_FLOAT if (is8ByteSlot(slotType)) slotSizeInfo->nonPointer64BitSlotCount += 1; else slotSizeInfo->nonPointer32BitSlotCount += 1; } else { #ifdef _DEBUG pointerSlots += 1; #endif } } break; } case TRAIT_Method: { Binding baseBinding = this->getOverride(basetb, ns, name, ne.tag, toplevel); if (baseBinding == BIND_NONE) { addVersionedBindings(bindings, name, compat_nss, AvmCore::makeMGSBinding(methodCount, BKIND_METHOD)); // accessors require 2 vtable slots, methods only need 1. methodCount += 1; } else if (AvmCore::isMethodBinding(baseBinding)) { // something got overridden, need new name entry for this subclass // but keep the existing disp_id addVersionedBindings(bindings, name, compat_nss, baseBinding); } else { if (toplevel) toplevel->throwVerifyError(kCorruptABCError); AvmAssert(!"unhandled verify error"); } break; } case TRAIT_Getter: case TRAIT_Setter: { // if nothing already is defined in this class, use base class in case getter/setter has already been defined. Binding baseBinding = bindings->get(name, ns); if (baseBinding == BIND_NONE) baseBinding = this->getOverride(basetb, ns, name, ne.tag, toplevel); const BindingKind us = (ne.kind == TRAIT_Getter) ? BKIND_GET : BKIND_SET; const BindingKind them = (ne.kind == TRAIT_Getter) ? BKIND_SET : BKIND_GET; if (baseBinding == BIND_NONE) { addVersionedBindings(bindings, name, compat_nss, AvmCore::makeMGSBinding(methodCount, us)); // accessors require 2 vtable slots, methods only need 1. methodCount += 2; } else if (AvmCore::isAccessorBinding(baseBinding)) { // something maybe got overridden, need new name entry for this subclass // but keep the existing disp_id // both get & set bindings use the get id. set_id = get_id + 1. if (AvmCore::bindingKind(baseBinding) == them) { baseBinding = AvmCore::makeGetSetBinding(baseBinding); } addVersionedBindings(bindings, name, compat_nss, baseBinding); } else { if (toplevel) toplevel->throwVerifyError(kCorruptABCError); AvmAssert(!"unhandled verify error"); } break; } default: // unsupported traits type -- can't happen, caught in AbcParser::parseTraits AvmAssert(0); break; } } // for i slotCount = sic.slotCount(); if (slotSizeInfo) { uint32_t const c = slotSizeInfo->nonPointer32BitSlotCount + slotSizeInfo->nonPointer64BitSlotCount + baseSlotCount + IFFLOAT(slotSizeInfo->nonPointer128BitSlotCount,0); // We calculate this at the end because we might have a sparse slot table... // the unused slots will be, well, unused, but we still have to reserve space // for them to guard against pathological code. slotSizeInfo->pointerSlotCount = slotCount - c; // Don't do this assertion: if this situation occurs, we'll catch it (and throw // a VerifyError) in finishSlotsAndMethods; asserting here will just spoil acceptance test runs. // AvmAssert(slotSizeInfo->pointerSlotCount >= pointerSlots); } } namespace { // Don't mess with the order of the members of the // following struct. // // This struct is never actually instantiated, it is // only used to determine the default padding that C++ // compiler will insert between 32 bit member variables and // 64 bit member variables. struct GlueClassTest_Slots { int32_t m_intSlot; double m_numberSlot; int32_t m_otherIntSlot; void* m_ptrSlot; }; static const bool align8ByteSlots = (offsetof(GlueClassTest_Slots, m_numberSlot) == 8); static const bool alignPointersTo8Bytes = (offsetof(GlueClassTest_Slots, m_ptrSlot) == 24); static const bool is64Bit = sizeof(void*) == 8; #ifdef VMCFG_FLOAT struct GlueClassTest128_Slots { int32_t m_intSlot; float4_t m_float4Slot; }; static const bool align16ByteSlots = offsetof(GlueClassTest128_Slots, m_float4Slot) >= 8; #ifdef _DEBUG struct AlmostStaticAsserts { AlmostStaticAsserts() { AvmAssert((offsetof(GlueClassTest_Slots, m_numberSlot) == 4) || (offsetof(GlueClassTest_Slots, m_numberSlot) == 8) ); AvmAssert((offsetof(GlueClassTest_Slots, m_otherIntSlot) == 12) || (offsetof(GlueClassTest_Slots, m_otherIntSlot) == 16) ); AvmAssert((offsetof(GlueClassTest_Slots, m_ptrSlot) == 16) || (offsetof(GlueClassTest_Slots , m_ptrSlot) == 20) || (offsetof(GlueClassTest_Slots, m_ptrSlot) == 24) ); AvmAssert((offsetof(GlueClassTest128_Slots, m_float4Slot) == 4) || (offsetof(GlueClassTest128_Slots, m_float4Slot) == 16) || (offsetof(GlueClassTest128_Slots, m_float4Slot) == 8) ); /* Is it EVER the case that pointers are 8-byte aligned, but doubles are not? We currently assume "no", but let's double-test this assumption */ struct GlueClassTestPtr_Slots { int32_t m_intSlot; void* m_ptrSlot; }; AvmAssert( ((offsetof(GlueClassTestPtr_Slots, m_ptrSlot) == 8) && alignPointersTo8Bytes) || ((offsetof(GlueClassTestPtr_Slots, m_ptrSlot) == 4) && !alignPointersTo8Bytes)); } } __unused_var; #endif #endif // VMCFG_FLOAT } // Sun compilers don't allow static and REALLY_INLINE #ifdef VMCFG_FLOAT /*static*/ REALLY_INLINE int32_t computeSlotOffset(Traits* slotType, int32_t& next32BitSlotOffset, int32_t& nextPointerSlotOffset, int32_t& next64BitSlotOffset, int32_t& next128BitSlotOffset) #else /*static*/ REALLY_INLINE int32_t computeSlotOffset(Traits* slotType, int32_t& next32BitSlotOffset, int32_t& nextPointerSlotOffset, int32_t& next64BitSlotOffset) #endif // VMCFG_FLOAT { if (isPointerSlot(slotType)) { int32_t const result = nextPointerSlotOffset; nextPointerSlotOffset += sizeof(void*); return result; } else if (is8ByteSlot(slotType)) { int32_t const result = next64BitSlotOffset; next64BitSlotOffset += 8; return result; } #ifdef VMCFG_FLOAT else if (is16ByteSlot(slotType)) { int32_t const result = next128BitSlotOffset; next128BitSlotOffset += 16; return result; } #endif // VMCFG_FLOAT else { int32_t const result = next32BitSlotOffset; next32BitSlotOffset += 4; return result; } } void Traits::computeNonNativeSlotAreaCountAndSize(TraitsBindings* tb, uint32_t& slotCount, uint32_t& size) const { const TraitsBindings* prevBindings = tb; const TraitsBindings* currBindings = tb->base; uint32_t thisSize = getSizeOfInstance(); while ((currBindings != NULL) && (currBindings->owner->getSizeOfInstance() == thisSize)) { const TraitsBindings* baseBindings = currBindings->base; AvmAssert((baseBindings == 0) || (baseBindings->owner->getSizeOfInstance() <= thisSize)); (void)baseBindings; prevBindings = currBindings; currBindings = currBindings->base; } // currBindings is first ancestor class that is native with at least one slot or native member variable // or null if there is no native ancenstor class if (currBindings == NULL) { // We could not find a ancestor class that is native. slotCount = tb->slotCount; size = tb->m_slotSize; } else if (currBindings == tb->base) { AvmAssert(tb->base != NULL); AvmAssert((tb->base->slotCount == 0) || (tb->base->owner->getSizeOfInstance() <= thisSize)); // "this" is a native class. slotCount = 0; size = 0; } else { AvmAssert(tb->base != NULL); AvmAssert(currBindings != NULL); AvmAssert(currBindings->owner->getSizeOfInstance() <= thisSize); AvmAssert(tb->slotCount >= prevBindings->slotCount); AvmAssert(tb->m_slotSize >= prevBindings->m_slotSize); slotCount = tb->slotCount - prevBindings->slotCount; size = tb->m_slotSize - prevBindings->m_slotSize; } } uint32_t Traits::finishSlotsAndMethods(TraitsBindingsp basetb, TraitsBindings* tb, const Toplevel* toplevel, const SlotSizeInfo& sizeInfo) const { const uint8_t* pos = traitsPosStart(); SlotIdCalcer sic(basetb ? basetb->slotCount : 0, this->allowEarlyBinding()); /* A word of caution about calculating slot layout: Say you have two classes like so: class A { ... }; class B : public A { int32 foo; }; You might assume that sizeof(B) > sizeof(A). However... if the contents of A are class A { char foo[9]; }; class B : public A { int32 bar; }; The compiler may decide that sizeof(A) should be rounded up to the nearest pointer-sized boundary (e.g. if it contains virtual methods)... so for a 64-bit build, sizeof(A) could be 16. So sizeof(B) is 20, right? Nope! Not necesssarily. Apparently GCC is clever enough to realize that there is enough unused (and appropriately aligned) space at the end of A to fit the extra fields in B... so sizeof(A) == sizeof(B) == 16. Similarly, we could have class A { char foo[9]; }; class B : public A { int32 bar[3]; }; which could yield sizeof(A) == 16, sizeof(B) == 24, which violates the previously-assumed invariant of (sizeof(B)-sizeof(A)>=slotsize(B)). (Note that this behavior is theoretically possible on 32-bit builds as well, but since the slots only contain 32-bit-or-larger fields, there can't ever be enough properly-aligned padding for this to happen.) Anyway, the point of this digression is to point out that you can't ever assume that our slotArea starts "after" our parent; there might be overlap. m_offsetofSlots is computed for all native classes using C++ offsetof(), and thus correctly accounts for any overlap of this sort. See bugzilla 655300 for more info. */ int32_t slotAreaStart = m_offsetofSlots; if (slotAreaStart == 0 && base != NULL) { // 0 means "put the slots at the end of my immediate parent" slotAreaStart = base->getHashtableOffset() ? base->getHashtableOffset() : base->getTotalSize(); } int32_t next32BitSlotOffset = slotAreaStart; int32_t endOf32BitSlots = next32BitSlotOffset + (sizeInfo.nonPointer32BitSlotCount * 4); int32_t nextPointerSlotOffset = alignPointersTo8Bytes && (sizeInfo.pointerSlotCount != 0) ? pad8(endOf32BitSlots) : endOf32BitSlots; int32_t endOfPointerSlots = nextPointerSlotOffset + (sizeInfo.pointerSlotCount * sizeof(void*)); int32_t next64BitSlotOffset = align8ByteSlots && (sizeInfo.nonPointer64BitSlotCount != 0) ? pad8(endOfPointerSlots) : endOfPointerSlots; int32_t endOf64BitSlots = next64BitSlotOffset + (sizeInfo.nonPointer64BitSlotCount * 8); #ifdef VMCFG_FLOAT int32_t next128BitSlotOffset = align16ByteSlots && (sizeInfo.nonPointer128BitSlotCount != 0) ? pad16(endOf64BitSlots) : endOf64BitSlots; int32_t endOf128BitSlots = next128BitSlotOffset + (sizeInfo.nonPointer128BitSlotCount * 16); #endif // VMCFG_FLOAT NameEntry ne; const uint32_t nameCount = pos ? AvmCore::readU32(pos) : 0; for (uint32_t i = 0; i < nameCount; i++) { AvmAssert(next32BitSlotOffset <= endOf32BitSlots); AvmAssert(nextPointerSlotOffset <= endOfPointerSlots); AvmAssert(next64BitSlotOffset <= endOf64BitSlots); FLOAT_ONLY(AvmAssert(next128BitSlotOffset <= endOf128BitSlots)); (void)IFFLOAT(endOf128BitSlots,endOf64BitSlots); ne.readNameEntry(pos); Multiname mn; this->pool->resolveBindingNameNoCheck(ne.qni, mn, toplevel); Namespacep ns = mn.getNamespace(); Stringp name = mn.getName(); // NOTE only one versioned namespace from the set needed here switch (ne.kind) { case TRAIT_Slot: case TRAIT_Const: case TRAIT_Class: { uint32_t slotid = sic.calc_id(ne.id); if (slotid >= tb->slotCount || tb->getSlotOffset(slotid) > 0) { if (toplevel) toplevel->throwVerifyError(kCorruptABCError); AvmAssert(!"unhandled verify error"); } // note, for TRAIT_Class, AbcParser::parseTraits has already verified that pool->cinits[ne.info] is not null Traitsp slotType = (ne.kind == TRAIT_Class) ? pool->getClassTraits(ne.info) : pool->resolveTypeName(ne.info, toplevel); uint32_t slotOffset = computeSlotOffset(slotType, next32BitSlotOffset, nextPointerSlotOffset, next64BitSlotOffset FLOAT_ONLY(, next128BitSlotOffset)); AvmAssert(slotOffset >= sizeof(ScriptObject)); tb->setSlotInfo(slotid, slotType, slotStorageType(getBuiltinType(slotType)), slotOffset); break; } case TRAIT_Getter: case TRAIT_Setter: case TRAIT_Method: { const Binding b = tb->m_bindings->get(name, ns); if (b != BIND_NONE) { const uint32_t disp_id = uint32_t(uintptr_t(b) >> 3) + (ne.kind == TRAIT_Setter); Binding base = this->getOverride(basetb, ns, name, ne.tag, toplevel); if (AvmCore::isMethodBinding(base) || (AvmCore::hasGetterBinding(base) && (ne.kind == TRAIT_Getter)) || (AvmCore::hasSetterBinding(base) && (ne.kind == TRAIT_Setter))) { ensureNonFinal(basetb->getMethod(disp_id), toplevel); } MethodInfo* f = this->pool->getMethodInfo(ne.id); //AvmAssert(f->declaringTraits() == this); tb->setMethodInfo(disp_id, f); } else { // This should only be BIND_NONE if the attribute was elided via Api Versioning. AvmAssert(!core->isValidApiVersion(ns->getApiVersion())); } break; } default: // unsupported traits type -- can't happen, caught in AbcParser::parseTraits AvmAssert(0); break; } } // for i // check for sparse slot table -- anything not specified will default to * (but we must allocate space for it) for (uint32_t i = 0; i < tb->slotCount; i++) { if (tb->getSlotOffset(i) > 0) continue; #ifdef AVMPLUS_VERBOSE if (pool->isVerbose(VB_traits)) { core->console << "WARNING: slot " << i+1 << " on " << this << " not defined by compiler. Using *\n"; } #endif const Traitsp slotType = NULL; const uint32_t slotOffset = nextPointerSlotOffset; nextPointerSlotOffset += sizeof(void*); AvmAssert(slotOffset >= sizeof(ScriptObject)); tb->setSlotInfo(i, slotType, SST_atom, slotOffset); } if (!(next32BitSlotOffset == endOf32BitSlots && nextPointerSlotOffset == endOfPointerSlots && next64BitSlotOffset == endOf64BitSlots && FLOAT_ONLY(next128BitSlotOffset == endOf128BitSlots &&) IFFLOAT(endOf128BitSlots,endOf64BitSlots) >= slotAreaStart)) { // Verify that we used exactly the space we predicted; if not, we may // have a fuzzed file that duplicate slots in certain orders. This is unacceptable, // as it may allow access to the same memory location with different type expectations. if (toplevel) toplevel->throwVerifyError(kCorruptABCError); AvmAssert(!"unhandled verify error"); } return IFFLOAT(endOf128BitSlots,endOf64BitSlots) - slotAreaStart; } static const uint8_t* skipToInterfaceCount(const uint8_t* pos) { AvmAssert(pos != NULL); AvmCore::skipU32(pos, 2); // skip the QName + basetraits const uint8_t theflags = *pos++; if (theflags & 8) AvmCore::skipU32(pos); // skip protected namespace return pos; } // Apps often have many interfaces redundantly listed, so first time thru, // eliminate redundant ones. We do this by only adding new interfaces to // the "seen" list and only traversing new super-interfaces. An interface // is NEW if our base class does not implement it. uint32_t Traits::countNewInterfaces(GCList& seen) { // each Traits* added to this list is rooted via Domain and PoolObject, // so it is okay for this Stack to be opaque to the GC. Stack pending; pending.add(this); while (!pending.isEmpty()) { Traits* t = pending.pop(); const uint8_t* pos = skipToInterfaceCount(t->m_traitsPos); const uint32_t interfaceCount = AvmCore::readU32(pos); for (uint32_t j = 0; j < interfaceCount; j++) { // we know all interfaces have been resolved already, it is done // before traits construction in AbcParser::parseInstanceInfos(). Traitsp intf = t->pool->resolveTypeName(pos, NULL); AvmAssert(intf && intf->isInterface() && intf->base == NULL); // an interface can "extend" multiple other interfaces, so we must recurse here. if ((!base || !base->subtypeof(intf)) && seen.indexOf(intf) < 0) { seen.add(intf); pending.add(intf); } } } return seen.length(); } static uint8_t calcLog2(uint32_t cap) { uint8_t capLog = 1; // start with at least 2 entries while ((1U<= cap); return capLog; } TraitsBindings* Traits::_buildTraitsBindings(const Toplevel* toplevel, bool includeTypes) { #ifdef AVMPLUS_VERBOSE if (pool->isVerbose(VB_traits)) { core->console << "Generate TraitsBindings for "<GetGC(); MultinameBindingHashtable* bindings = MultinameBindingHashtable::create(gc, bindingCap); AvmAssert(bindings->numQuads == bindingCap); if (this->posType() == TRAITSTYPE_CATCH) { const uint8_t* pos = m_traitsPos; Traits* t = this->pool->resolveTypeName(pos, toplevel); // this assumes we save name/ns in all builds, not just verbose NamespaceSetp nss = NamespaceSet::create(core->GetGC(), this->ns()); NamespaceSetp compat_nss = nss; addVersionedBindings(bindings, this->name(), compat_nss, AvmCore::makeSlotBinding(0, BKIND_VAR)); // bindings just need room for one slot binding thisData = TraitsBindings::alloc(gc, this, /*base*/NULL, bindings, /*slotCount*/1, /*methodCount*/0, true); thisData->setSlotInfo(0, t, slotStorageType(getBuiltinType(t)), this->m_sizeofInstance); thisData->m_slotSize = is8ByteSlot(t) ? 8 : 4; } else { TraitsBindingsp basetb = this->base ? this->base->getTraitsBindings() : NULL; // Copy protected traits from base class into new protected namespace if (basetb && base->protectedNamespace && this->protectedNamespace) { StTraitsBindingsIterator iter(basetb); while (iter.next()) { if (!iter.key()) continue; if (iter.ns() == base->protectedNamespace) { bindings->add(iter.key(), this->protectedNamespace, iter.value()); } } } SlotSizeInfo _slotSizeInfo; SlotSizeInfo* slotSizeInfo = includeTypes ? &_slotSizeInfo : NULL; uint32_t slotCount = 0; uint32_t methodCount = 0; buildBindings(basetb, bindings, slotCount, methodCount, slotSizeInfo, toplevel); thisData = TraitsBindings::alloc(gc, this, basetb, bindings, slotCount, methodCount, includeTypes); if (slotSizeInfo) { thisData->m_slotSize = finishSlotsAndMethods(basetb, thisData, toplevel, *slotSizeInfo); if (basetb) thisData->m_slotSize += basetb->m_slotSize; } if (!isInterface() && m_implementsNewInterfaces) { // fix up interface bindings for (InterfaceIterator it(this); it.hasNext(); ) { Traits* t = it.next(); if (!base || !base->subtypeof(t)) { // new interface not implemented in base. thisData->fixOneInterfaceBindings(t); } } } } // hashtable (if we have one) must start on pointer-sized boundary... // much easier to always round slotsize up unconditionally rather than // only for cases with hashtable, so that's what we'll do. (MMgc currently // allocate in 8-byte increments anyway, so we're not really losing any space.) // (only really necessary to do this if we calced slotSizeInfo, but simpler & harmless // to just do it unconditionally) thisData->m_slotSize = (thisData->m_slotSize + (sizeof(uintptr_t)-1)) & ~(sizeof(uintptr_t)-1); // remember the cap we need if (m_bindingCapLog2 == 0) m_bindingCapLog2 = calcLog2(thisData->m_bindings->numQuads); // remember capacity, not count AvmAssert(m_bindingCapLog2 > 0); #ifdef AVMPLUS_VERBOSE if (pool->isVerbose(VB_traits)) { core->console << this << " bindings\n"; StTraitsBindingsIterator iter(thisData); while (iter.next()) { core->console << iter.key() << ":" << (uint32_t)(uintptr_t)(iter.value()) << "\n"; } core->console << this << " end bindings \n"; } #endif AvmAssert(m_tbref->isNull()); m_tbref = thisData->GetWeakRef(); core->tbCache()->add(thisData); return thisData; } TraitsMetadata* Traits::_buildTraitsMetadata() { #ifdef AVMPLUS_VERBOSE if (pool->isVerbose(VB_traits)) { core->console << "Generate TraitsMetadata for "<getTraitsBindings(); MMgc::GC* gc = core->GetGC(); TraitsMetadatap basetm = this->base ? this->base->getTraitsMetadata() : NULL; const uint32_t extra = td->slotCount * sizeof(TraitsMetadata::MetadataPtr) + td->methodCount * sizeof(TraitsMetadata::MetadataPtr); TraitsMetadata* tm = TraitsMetadata::create(gc, extra, basetm, this->pool, this->metadata_pos, td->slotCount, td->methodCount); tm->slotMetadataPos = (TraitsMetadata::MetadataPtr*)(tm + 1); tm->methodMetadataPos = (TraitsMetadata::MetadataPtr*)(tm->slotMetadataPos + tm->slotCount); const uint8_t* pos = traitsPosStart(); const uint32_t nameCount = pos ? AvmCore::readU32(pos) : 0; SlotIdCalcer sic(td->base ? td->base->slotCount : 0, this->allowEarlyBinding()); NameEntry ne; for (uint32_t i = 0; i < nameCount; i++) { ne.readNameEntry(pos); switch (ne.kind) { case TRAIT_Class: AvmAssert(0); // classes shouldn't have metadata, but just fall thru just in case case TRAIT_Slot: case TRAIT_Const: { const uint32_t slot_id = sic.calc_id(ne.id); if (ne.tag & ATTR_metadata) tm->slotMetadataPos[slot_id] = ne.meta_pos; break; } case TRAIT_Getter: case TRAIT_Setter: case TRAIT_Method: { if (ne.tag & ATTR_metadata) { Multiname qn; // passing NULL for toplevel here, since it's only used if a verification error occurs -- // but if there was one, we would have encountered it during AbcParser::parseTraits. this->pool->resolveBindingNameNoCheck(ne.qni, qn, /*toplevel*/NULL); const Binding b = td->findBinding(qn.getName(), qn.getNamespace()); AvmAssert(b != BIND_NONE); const uint32_t disp_id = uint32_t(uintptr_t(b) >> 3) + (ne.kind == TRAIT_Setter); tm->methodMetadataPos[disp_id] = ne.meta_pos; } break; } default: // unsupported traits type -- can't happen, caught in AbcParser::parseTraits AvmAssert(0); break; } } // for i AvmAssert(m_tmref->isNull()); m_tmref = tm->GetWeakRef(); core->tmCache()->add(tm); return tm; } /** * add t to pending[] ahead of any existing entries that are * subtypes or implementers of t, so that when we iterate through * pending[] we will traverse the inheritance DAG top-down. */ void insertSupertype(Traits* t, GCList &pending) { uint32_t i = 0; for (uint32_t n = pending.length(); i < n; i++) { if (pending[i]->subtypeof(t)) { pending.insert(i, t); return; } } pending.add(t); } /** * resolve this traits signatures by visiting all supertypes in * top-down toplogical order, then resolving our own signatures. The top-down * order ensures that we resolve base classes and interfaces first, while * avoiding recursion. */ void Traits::resolveSignatures(const Toplevel* toplevel) { // toplevel actually can be null, when resolving the builtin classes... // but they should never cause verification errors in functioning builds if (m_resolved) return; GCList pending(core->gc, kListInitialCapacity); // copy primary supertypes into pending. // primary_supertypes[] is already in top-down order. for (int32_t i = 0; i < MAX_PRIMARY_SUPERTYPE; i++) { Traits* t = m_primary_supertypes[i]; if (t == NULL || t == this) break; if (!t->m_resolved) pending.add(t); } // copy other base types, and interfaces, into pending[] by // and maintaining the partial ordering that each type's bases and // interfaces are visited before that type itself is visited. for (Traits** st = get_secondary_supertypes(); *st != NULL; st++) { Traits* t = *st; if (t != this && !t->m_resolved) insertSupertype(t, pending); } for (uint32_t i = 0, n = pending.length(); i < n; i++) { AvmAssert(!pending[i]->m_resolved); pending[i]->resolveSignaturesSelf(toplevel); } this->resolveSignaturesSelf(toplevel); } void Traits::verifyBindings(const Toplevel* toplevel) { // builtin Traits get called before a Toplevel is created, so they // get a free pass. Everyone else better pass a good value. AvmAssert(toplevel != NULL || pool->isBuiltin); #ifdef DEBUG AvmAssert(!m_bindingsVerified); // make sure our supertypes have verified their bindings. (must be done before calling _buildTraitsBindings) for (Traits* t = this->base; t != NULL; t = t->base) AvmAssert(t->m_bindingsVerified); for (Traits** st = this->get_secondary_supertypes(); *st != NULL; st++) AvmAssert(*st == this || (*st)->m_bindingsVerified); #endif AvmAssert(m_tbref == core->GetGC()->emptyWeakRef); // force buildTraitsBindings to run now, so that any errors will // also be reported now, instead of later when called on a QCache // miss, when we don't have a valid Toplevel* for exception-throwing context. _buildTraitsBindings(toplevel, /*includeTypes*/false); #ifdef DEBUG this->m_bindingsVerified = true; #endif } /** * This must be called before any method is verified or any * instances are created. It is not done eagerly in AbcParser * because doing so would prevent circular type references between * slots of cooperating classes. * * Resolve the type and position/width of each slot. */ void Traits::resolveSignaturesSelf(const Toplevel* toplevel) { // builtin Traits get called before a Toplevel is created, so they // get a free pass. Everyone else better pass a good value. AvmAssert(toplevel != NULL || pool->isBuiltin); AvmAssert(m_bindingsVerified); #ifdef DEBUG AvmAssert(!m_resolved); // make sure our supertypes are resolved. (must be done before calling _buildTraitsBindings) for (Traits* t = this->base; t != NULL; t = t->base) AvmAssert(t->m_resolved); for (Traits** st = this->get_secondary_supertypes(); *st != NULL; st++) AvmAssert(*st == this || (*st)->m_resolved); #endif // we might already have one -- if so, toss it m_tbref = core->GetGC()->emptyWeakRef; TraitsBindings* tb = _buildTraitsBindings(toplevel, /*includeTypes*/true); genInitBody(toplevel, tb); // leave m_tmref as empty, we don't need it yet uint32_t slotAreaSize = 0; uint32_t slotAreaCount = 0; switch (posType()) { case TRAITSTYPE_INTERFACE: case TRAITSTYPE_INSTANCE: case TRAITSTYPE_CLASS: case TRAITSTYPE_SCRIPT: case TRAITSTYPE_ACTIVATION: case TRAITSTYPE_CATCH: computeNonNativeSlotAreaCountAndSize(tb, slotAreaCount, slotAreaSize); m_totalSize = getSizeOfInstance() + slotAreaSize; break; case TRAITSTYPE_NVA: case TRAITSTYPE_RT: m_totalSize = getSizeOfInstance(); break; } AvmAssert(m_totalSize >= m_sizeofInstance); if (m_needsHashtable || (base && base->base && base->m_hashTableOffset && !isXMLType())) { // round up total size to multiple of pointer size. m_totalSize = ((m_totalSize+(sizeof(uintptr_t)-1))&~(sizeof(uintptr_t)-1)); m_hashTableOffset = m_totalSize; m_totalSize += sizeof(InlineHashtable); AvmAssert(builtinType == BUILTIN_boolean ? true : (m_hashTableOffset & 3) == 0); AvmAssert((m_hashTableOffset & (sizeof(uintptr_t)-1)) == 0); AvmAssert((m_totalSize & (sizeof(uintptr_t)-1)) == 0); } // make sure all the methods have resolved types if (tb->methodCount) { const TraitsBindings::BindingMethodInfo* tbm = tb->getMethods(); const TraitsBindings::BindingMethodInfo* tbm_end = tbm + tb->methodCount; for ( ; tbm < tbm_end; ++tbm) { // don't assert: could be null if only one of a get/set pair is implemented //AvmAssert(tbm->f != NULL); if (tbm->f != NULL) { tbm->f->resolveSignature(toplevel); } } } if (this->init != NULL) this->init->resolveSignature(toplevel); bool legal = true; TraitsBindingsp tbbase = tb->base; // might be null if (tbbase && tbbase->methodCount > 0) { // check concrete overrides const TraitsBindings::BindingMethodInfo* basetbm = tbbase->getMethods(); const TraitsBindings::BindingMethodInfo* basetbm_end = basetbm + tbbase->methodCount; const TraitsBindings::BindingMethodInfo* tbm = tb->getMethods(); for ( ; basetbm < basetbm_end; ++basetbm, ++tbm) { if (basetbm->f != NULL && basetbm->f != tbm->f) legal &= tb->checkOverride(core, basetbm->f, tbm->f); } } if (legal && !this->isInterface()) { legal &= tb->checkLegalInterfaces(core); } if (!legal) { AvmAssert(!m_resolved); Multiname qname(ns(), name()); if (toplevel) toplevel->throwVerifyError(kIllegalOverrideError, core->toErrorString(&qname), core->toErrorString(this)); #ifdef VMCFG_VERIFYALL else { if (core->config.verifyonly) core->console << "ILLEGAL OVERRIDE\n"; } #endif AvmAssert(!"unhandled verify error"); } tb->buildSlotDestroyInfo(core->GetGC(), m_slotDestroyInfo, slotAreaCount, slotAreaSize); m_resolved = true; #ifdef AVMPLUS_VERBOSE if (pool->isVerbose(VB_traits)) { core->console << "Resolved "; printExtended(core->console) << "\n"; } #endif } #ifdef AVMPLUS_VERBOSE PrintWriter& Traits::printExtended(PrintWriter& pw) { const char* desc = NULL; switch (posType()) { case TRAITSTYPE_INTERFACE: desc = "interface"; break; case TRAITSTYPE_INSTANCE: desc = "instance"; break; case TRAITSTYPE_CLASS: desc = "class"; break; case TRAITSTYPE_SCRIPT: desc = "script"; break; case TRAITSTYPE_ACTIVATION: desc = "activation"; break; case TRAITSTYPE_CATCH: desc = "catch"; break; case TRAITSTYPE_NVA: // null/void/any desc = "singleton"; break; case TRAITSTYPE_RT: desc = "rt"; break; default: AvmAssert(false); } const char* domainString = core->identifyDomain(pool->domain); pw << ns() << "::" << name() << ", type:" << desc << ",domain:"; if (domainString) { pw << domainString; } else { // not a well known domain, print the address to help distinguish distinct domains pw << "@" << hexAddr((intptr_t)(Domain*)pool->domain); } return pw; } #endif #ifdef VMCFG_AOT static inline void hookUpActivationTraitsInitMethodForTraitsMethod(AvmCore* core, MethodInfo* m) { AvmAssert(m->needActivation()); const AOTInfo* aotInfo = m->pool()->aotInfo; Traits* activationTraits = m->activationTraits(); AvmAssert(activationTraits != NULL); AvmAssert(m->method_id() < (int)aotInfo->nActivationTraits); AvmAssert(aotInfo->activationTraits != NULL); AvmAssert(aotInfo->activationTraits[m->method_id()] == activationTraits); AvmAssert(aotInfo->activationInfo != NULL); // See comment in initActivationTraits about why this can be called more than once per Traits if (activationTraits->init == NULL) { if (aotInfo->activationInfo[m->method_id()].initHandler) { // NativeMethodInfo.handler is a union of // pointer to function and pointer to member function. // Zero the structure so that the entire thing is // initialized. // See bugzilla#647660 NativeMethodInfo compiledMethodInfo = {{0}, 0, 0}; compiledMethodInfo.thunker = aotThunker; AvmThunkNativeHandler nhandler; nhandler.function = aotInfo->activationInfo[m->method_id()].initHandler; activationTraits->init = MethodInfo::create(core->GetGC(), MethodInfo::kInitMethodStub, activationTraits, &compiledMethodInfo, nhandler, aotInfo->activationInfo[m->method_id()].initMethodId); } // The following comes from Verifier::write() TODO: refactor so we can share this code const ScopeTypeChain *scope = m->activationScope(); if (scope == NULL) { scope = m->declaringScope()->cloneWithNewTraits(core->GetGC(), activationTraits); activationTraits->setDeclaringScopes(scope); m->init_activationScope(scope); } } } void Traits::initActivationTraits() { // Note: this can be called multiple times per Traits from initScript, which must call this in case it's needed // but is itself called once per Toplevel const uint8_t* pos = traitsPosStart(); if (this->init->needActivation()) { hookUpActivationTraitsInitMethodForTraitsMethod(core, this->init); } NameEntry ne; const uint32_t nameCount = pos ? AvmCore::readU32(pos) : 0; for (uint32_t i = 0; i < nameCount; i++) { ne.readNameEntry(pos); switch (ne.kind) { case TRAIT_Slot: case TRAIT_Const: case TRAIT_Class: break; case TRAIT_Method: case TRAIT_Getter: case TRAIT_Setter: { MethodInfo* m = pool->getMethodInfo(ne.id); if (m->needActivation()) { hookUpActivationTraitsInitMethodForTraitsMethod(core, m); } break; } default: // unsupported traits type -- can't happen, caught in AbcParser::parseTraits AvmAssert(0); break; } } } #endif // static bool Traits::isMachineCompatible(const Traits* a, const Traits* b) { return (a == b) || // *, Object, and Void are each represented as Atom ((!a || a->builtinType == BUILTIN_object || a->builtinType == BUILTIN_void) && (!b || b->builtinType == BUILTIN_object || b->builtinType == BUILTIN_void)) || // all other non-pointer types have unique representations (a && b && !a->isMachineType() && !b->isMachineType()); } PrintWriter& Traits::print(PrintWriter& prw, bool includeAllNamespaces) const { if (name() != NULL) { Multiname::print(prw, ns(), name(), false, !includeAllNamespaces); } else { prw << "Traits@" << asAtomHex((Atom)this); } return prw; } void Traits::genInitBody(const Toplevel* toplevel, TraitsBindings* tb) { #if defined(VMCFG_AOT) if (this->init && this->init->isAotCompiled()) return; #endif struct ValidateInitVisitor: public InitVisitor { bool hasDefaults; ValidateInitVisitor() : hasDefaults(false) {} virtual ~ValidateInitVisitor() {} void defaultVal(Atom, uint32_t, Traits*) { hasDefaults = true; } }; ValidateInitVisitor visitor; visitInitBody(&visitor, toplevel, tb); // if initialization code gen is required, create a new method body and write it to traits->init->body_pos if (visitor.hasDefaults && !this->init) { // Make a do-nothing init method. this->init = MethodInfo::create(core->gc, MethodInfo::kInitMethodStub, this); AvmAssert(this->init->declaringTraits() == this); static const uint8_t body[] = { 2, // max_stack 1, // local_count 1, // init_scope_depth 1, // max_scope_depth 1, // code_length (uint8_t)OP_returnvoid, // code 0 // exception_count // the verifier and interpreter don't read the activation traits so stop here }; // The synthetic MethodInfo points directly to the static const ABC above. this->init->set_abc_body_pos(body); } } void Traits::visitInitBody(InitVisitor *visitor, const Toplevel* toplevel, const TraitsBindings* tb) { const uint8_t* pos = traitsPosStart(); SlotIdCalcer sic(tb->base ? tb->base->slotCount : 0, this->allowEarlyBinding()); NameEntry ne; const uint32_t nameCount = pos ? AvmCore::readU32(pos) : 0; for (uint32_t i = 0; i < nameCount; i++) { ne.readNameEntry(pos); switch (ne.kind) { case TRAIT_Slot: case TRAIT_Const: case TRAIT_Class: { uint32_t slotid = sic.calc_id(ne.id); // note, for TRAIT_Class, AbcParser::parseTraits has already verified that pool->cinits[ne.info] is not null Traitsp slotType = (ne.kind == TRAIT_Class) ? pool->getClassTraits(ne.info) : pool->resolveTypeName(ne.info, NULL); uint32_t value_index = ne.value_index; CPoolKind kind = ne.value_kind; Atom value = pool->getLegalDefaultValue(toplevel, value_index, kind, slotType); switch (Traits::getBuiltinType(slotType)) { case BUILTIN_any: case BUILTIN_object: if (value == 0) continue; break; #ifdef VMCFG_FLOAT case BUILTIN_float: if (*(int32_t*)atomPtr(value) == 0) continue; break; case BUILTIN_float4: { int32_t * fptr = (int32_t*)atomPtr(value); if (fptr[0] == 0 && fptr[1] == 0 && fptr[2] == 0 && fptr[3] == 0) continue; break; } #endif // VMCFG_FLOAT case BUILTIN_number: { double val = AvmCore::number_d(value); if ( val == 0 && !MathUtils::isNegZero(val)) continue; break; } case BUILTIN_boolean: AvmAssert((uintptr_t(falseAtom)>>3) == 0); if (value == falseAtom) continue; AvmAssert(value == trueAtom); break; case BUILTIN_uint: case BUILTIN_int: if (value == zeroIntAtom) continue; break; case BUILTIN_namespace: case BUILTIN_string: default: if (AvmCore::isNull(value)) continue; break; } visitor->defaultVal(value, slotid, slotType); break; } default: break; } } // for i } void Traits::destroyInstance(ScriptObject* obj) const { AvmAssert(m_resolved); InlineHashtable* ht = m_hashTableOffset ? obj->getTableNoInit() : NULL; // start by clearing native space to zero (except baseclasses) union { char* p_8; uint32_t* p; }; p_8 = (char*)obj + sizeof(AvmPlusScriptableObject); AvmAssert((uintptr_t(p) % sizeof(uint32_t)) == 0); if (!m_slotDestroyInfo.test(0)) { AvmAssert(m_slotDestroyInfo.cap() == 1); AvmAssert(m_totalSize >= (sizeof(AvmPlusScriptableObject) + (ht ? sizeof(InlineHashtable) : 0))); uint32_t sizeToZero = m_totalSize - (sizeof(AvmPlusScriptableObject) + (ht ? sizeof(InlineHashtable) : 0)); AvmAssert((sizeToZero % sizeof(uint32_t)) == 0); // we assume all sizes are multiples of sizeof(uint32_t) // no RCObjects, so just zero it all... my, that was easy VMPI_memset(p, 0, sizeToZero); } else { uint32_t sizeToZero = m_sizeofInstance - uint32_t(sizeof(AvmPlusScriptableObject)); AvmAssert((sizeToZero % sizeof(uint32_t)) == 0); // we assume all sizes are multiples of sizeof(uint32_t) VMPI_memset(p, 0, sizeToZero); p += (sizeToZero / sizeof(uint32_t)); AvmAssert(m_slotDestroyInfo.cap() >= 1); AvmAssert((uintptr_t(p) % sizeof(uint32_t)) == 0); const uint32_t slotAreaSize = getSlotAreaSize(); const uint32_t bitsUsed = slotAreaSize / sizeof(uint32_t); // not sizeof(Atom)! for (uint32_t bit = 1; bit <= bitsUsed; bit++) { if (m_slotDestroyInfo.test(bit)) { #ifdef AVMPLUS_64BIT AvmAssert((uintptr_t(p) & 7) == 0); // we had better be on an 8-byte boundary... #endif Atom a = *(const Atom*)p; RCObject* rc = NULL; if (atomKind(a) <= kNamespaceType) { rc = (RCObject*)atomPtr(a); if (rc) { AvmAssert(GC::GetGC(obj)->IsRCObject(rc)); rc->DecrementRef(); } } } *p++ = 0; } } // finally, zap the hashtable (if any) if(ht) { ht->destroy(); } //for DictionaryObject also zero out the //hashtable pointer stored at the offset address; if(isDictionary()) { union { char* p_8; uintptr_t* ptr; }; p_8 = (char*)obj + m_hashTableOffset; *ptr = 0; } } void Traits::traceSlots(MMgc::GC* gc, ScriptObject* obj) const { if (m_slotDestroyInfo.test(0)) { const uint32_t slotAreaSize = getSlotAreaSize(); union { char* p_8; uint32_t* p; }; p_8 = (char*)obj + m_sizeofInstance; AvmAssert(m_slotDestroyInfo.cap() >= 1); AvmAssert((uintptr_t(p) & 3) == 0); const uint32_t bitsUsed = slotAreaSize / sizeof(uint32_t); // not sizeof(Atom)! traceSlotsFromBitmap(gc, p, m_slotDestroyInfo, bitsUsed); } } // OPTIMIZEME: We can do better than traceInfo.test, which is probably // going to be about a dozen instructions on interesting architectures. // A fairly trivial optimization would be to provide an API on FixedBitSet // that provides uint32_t values representing sets of 32 bits, and // make the loop here doubly nested. That would also allow us to skip // quickly areas of objects that have all non-pointer data. void Traits::traceSlotsFromBitmap(MMgc::GC* gc, uint32_t* p, const FixedBitSet& traceInfo, uint32_t bitsUsed) const { for (uint32_t bit = 1; bit <= bitsUsed; bit++) { if (traceInfo.test(bit)) { #ifdef AVMPLUS_64BIT AvmAssert((uintptr_t(p) & 7) == 0); // we had better be on an 8-byte boundary... #endif avmplus::Atom a = *(const avmplus::Atom*)p; if (atomKind(a) <= avmplus::AtomConstants::kNamespaceType) { // This includes untagged pointers to String, Namespace, ScriptObject. AvmAssert(atomPtr(a) == NULL || gc->IsRCObject(atomPtr(a))); gc->TracePointer(atomPtr(a) HEAP_GRAPH_ARG((uintptr_t*)p)); } else if (atomKind(a) == avmplus::AtomConstants::kDoubleType) { AvmAssert(atomPtr(a) != NULL); gc->TracePointer(atomPtr(a) HEAP_GRAPH_ARG((uintptr_t*)p)); } else if (atomKind(a) == avmplus::AtomConstants::kSpecialBibopType && a != undefinedAtom) { AvmAssert(bibopKind(a) == avmplus::AtomConstants::kBibopFloatType || bibopKind(a) == avmplus::AtomConstants::kBibopFloat4Type); gc->TracePointer(atomPtr(a) HEAP_GRAPH_ARG((uintptr_t*)p)); } } p++; } } Stringp Traits::formatClassName() { #ifdef VMCFG_CACHE_GQCN if (_fullname != NULL) return _fullname; #endif Multiname qname(ns(), name()); qname.setQName(); StringBuffer buffer(core); buffer << qname; int length = buffer.length(); if (length && buffer.c_str()[length-1] == '$') { length--; } #ifndef VMCFG_CACHE_GQCN Stringp _fullname; #endif _fullname = core->newStringUTF8(buffer.c_str(), length); return _fullname; } Binding Traits::getOverride(TraitsBindingsp basetb, Namespacep ns, Stringp name, int tag, const Toplevel* toplevel) const { Binding baseBinding = BIND_NONE; if (base) { const Namespacep lookupNS = (protectedNamespace == ns && base->protectedNamespace) ? (Namespacep)base->protectedNamespace : ns; AvmAssert(basetb != NULL); baseBinding = basetb->findBinding(name, lookupNS); } const BindingKind baseBindingKind = AvmCore::bindingKind(baseBinding); const TraitKind kind = TraitKind(tag & 0x0f); // some extra-picky compilers will complain about the values being out of // range for the comparison (if we use "kind") even with explicit int casting. // so recycle the expression for the assert. AvmAssert((tag & 0x0f) >= 0 && (tag & 0x0f) < TRAIT_COUNT); static const uint8_t kDesiredKind[TRAIT_COUNT] = { BKIND_NONE, // TRAIT_Slot BKIND_METHOD, // TRAIT_Method BKIND_GET, // TRAIT_Getter BKIND_SET, // TRAIT_Setter BKIND_NONE, // TRAIT_Class BKIND_NONE, // TRAIT_Function BKIND_NONE // TRAIT_Const }; const BindingKind desiredKind = BindingKind(kDesiredKind[kind]); const uint8_t dkMask = uint8_t(1 << desiredKind); // given baseBindingKind, what are legal desiredKinds? static const uint8_t kLegalBaseKinds[8] = { (1<isVerbose(VB_traits) || pool->core->config.verifyonly) core->console << "illegal override in "<< this << ": " << Multiname(ns,name) <<"\n"; #endif if (toplevel) toplevel->throwVerifyError(kIllegalOverrideError, toplevel->core()->toErrorString(Multiname(ns,name)), toplevel->core()->toErrorString(this)); AvmAssert(!"unhandled verify error"); return BIND_NONE; } void Traits::ensureNonFinal(MethodInfo* minfo, const Toplevel* toplevel) const { if (!minfo->isFinal()) return; #ifdef AVMPLUS_VERBOSE if (pool->isVerbose(VB_traits)) core->console << "illegal override of final "<< minfo << " in " << this <<"\n"; #endif toplevel->throwVerifyError(kIllegalOverrideError, toplevel->core()->toErrorString(minfo), toplevel->core()->toErrorString(this)); AvmAssert(!"unhandled verify error"); } TraitsBindings* FASTCALL Traits::_getTraitsBindings() { AvmAssert(m_bindingsVerified); TraitsBindings* tb = _buildTraitsBindings(/*toplevel*/NULL, /*includeTypes*/m_resolved); return tb; } TraitsMetadata* FASTCALL Traits::_getTraitsMetadata() { TraitsMetadata* tm = _buildTraitsMetadata(); return tm; } // Count supertypes in the given list, like strlen(). static uint32_t countSupertypes(Traits** list) { uint32_t n = 0; for (Traits** t = list; *t != NULL; t++) n++; return n; } // Initialize the m_primary_supertypes array by copying down the entries // from our base class (if it exists), and adding this traits if there is room. void Traits::build_primary_supertypes() { MMGC_STATIC_ASSERT(offsetof(Traits, m_primary_supertypes) + sizeof(m_primary_supertypes) < 256); MMGC_STATIC_ASSERT(offsetof(Traits, m_supertype_cache) < 256); // compute m_supertype_offset and fill in m_primary_supertypes if (!base) { // class roots and interfaces m_supertype_offset = isInterface() ? offsetof(Traits, m_supertype_cache) : offsetof(Traits, m_primary_supertypes); WB(core->gc, this, &m_primary_supertypes[0], this); } else { // single inherited classes AvmAssert(!isInterface()); for (int i=0; i < MAX_PRIMARY_SUPERTYPE; i++) WB(core->gc, this, &m_primary_supertypes[i], base->m_primary_supertypes[i]); size_t off = base->m_supertype_offset; if (off != offsetof(Traits, m_supertype_cache) && (off += sizeof(Traits*)) - offsetof(Traits, m_primary_supertypes) < sizeof(m_primary_supertypes)) { AvmAssert(off == (uint8_t) off); m_supertype_offset = uint8_t(off); WB(core->gc, this, (Traits*)(uintptr_t(this)+off), this); } else { // Inheritance is too deep to add this traits to m_primary_supertypes. // Make this traits "secondary", set m_supertype_offset to the cache. m_supertype_offset = offsetof(Traits, m_supertype_cache); } } } // Initialize the m_secondary_supertypes array as follows: // * if this traits adds a new interface, create a new list with all // the entries from the base class, plus the base class itself, // plus the new interfaces // * if we do not add new interfaces and there's no base class, // use the empty list. // * if there is a base class, and the base class is primary, just // copy base->m_secondary_supertypes (containing any base interfaces) // * otherwise create a new secondary_supertypes list with everything // from the base class, plus the base class itself. Install this list // back in the base class so it can be shared by other leaf classes. // // allocSupertypeList returns un-scanned memory as we expect the // super types to be otherwise reachable to the GC, we also use // WB_SKIP to tell the GC we're skipping the WB on purpose. void Traits::build_secondary_supertypes() { MMgc::GC* gc = core->GetGC(); GCList seen(gc, kListInitialCapacity); uint32_t count; if (!isInstanceType() || (count = countNewInterfaces(seen)) == 0) { // no new interfaces, attempt to share the base type's secondary list if (!base) { WB(gc, this, &this->m_secondary_supertypes, core->_emptySupertypeList); } else { Traits** base_list = base->get_secondary_supertypes(); // If we require base in our secondary_supertypes list, so will other // sibling leaf types. Try to share the seconary_supertypes list by // inserting base at position 0. if (base->isPrimary() || base_list[0] == base) { // just copy the base list. m_secondary_supertypes = base->m_secondary_supertypes; } else { // must prepend base to base_list, save the copy on this type and base. count = countSupertypes(base_list); UnscannedTraitsArray* list = allocSupertypeList(gc, count + 1); Traits** raw_list = list->AsArray(); WB_SKIP(gc, raw_list, raw_list, base); for (uint32_t i=0; i < count; i++) WB_SKIP(gc, raw_list, raw_list+i+1, base_list[i]); base->m_secondary_supertypes = list; m_secondary_supertypes = list; } } } else { // this type implements new interfaces so we need a new list this->m_implementsNewInterfaces = true; if (base && !base->isPrimary() && base->get_secondary_supertypes()[0] != base) { seen.add(base); count++; } UnscannedTraitsArray* list; uint32_t baseCount = base ? countSupertypes(base->get_secondary_supertypes()) : 0; if (baseCount > 0) { uint32_t total = count + baseCount; list = allocSupertypeList(gc, total); for (Traits **d = list->AsArray(), **s = base->get_secondary_supertypes(); *s != NULL; s++, d++) WB_SKIP(gc, list, d, *s); } else { list = allocSupertypeList(gc, count); } m_secondary_supertypes = list; Traits** raw_list = list->AsArray(); for (uint32_t i=0; i < count; i++) { WB_SKIP(gc, raw_list, raw_list+baseCount+i, seen[i]); } } #ifdef DEBUG // sanity check to make sure we don't have any duplicate supertypes. GCList supertypes(gc, kListInitialCapacity); for (int i = 0; i < MAX_PRIMARY_SUPERTYPE; i++) { Traits* t = m_primary_supertypes[i]; if (t != NULL) { if (supertypes.indexOf(t) != -1) { core->console << "t " << this << " dup primary " << t << "\n"; AvmAssert(false); } supertypes.add(t); } } for (Traits** st = get_secondary_supertypes(); *st != NULL; st++) { Traits* t = *st; if (supertypes.indexOf(t) != -1) { core->console << "t " << this << " dup secondary " << t << "\n"; AvmAssert(false); } supertypes.add(t); } #endif } // search interfaces and bases that didn't fit in m_primary_supertypes, // and cache positive/negative results bool Traits::secondary_subtypeof(Traits* t) { for (Traits** s = get_secondary_supertypes(); *s != NULL; s++) { if (t == *s) { m_supertype_cache = t; return true; } } m_supertype_neg_cache = t; return false; } // create a new supertype list of the given length UnscannedTraitsArray* Traits::allocSupertypeList(GC* gc, uint32_t size) { // This memory is intentionally unscanned, see comments in header. return UnscannedTraitsArray::New(gc, size+1); } // Returns true if a value of type rhs can be assigned to a variable // or parameter of type lhs without modifying the representation of the value. // It is not just a subtypeof test, for example int is a subtype of Object // but they use different representations. bool Traits::canAssign(Traits* lhs, Traits* rhs) { if (!Traits::isMachineCompatible(lhs,rhs)) { // no machine type is assignment-compatible with any other return false; } if (!lhs) return true; // type on right must be same class or subclass of type on left. Traits* t = rhs; while (t != lhs && t != NULL) t = t->base; return t != NULL; } // Return the slot type (if slot), return type (if getter), or NULL. Traits* Traits::readBinding(Traits* traits, Binding b) { AvmAssert((!traits && AvmCore::bindingKind(b) == BKIND_NONE) || (traits && traits->isResolved())); switch (AvmCore::bindingKind(b)) { default: AvmAssert(false); // internal error - illegal binding type case BKIND_GET: case BKIND_GETSET: { int m = AvmCore::bindingToGetterId(b); MethodInfo *f = traits->getTraitsBindings()->getMethod(m); MethodSignaturep fms = f->getMethodSignature(); return fms->returnTraits(); } case BKIND_SET: // TODO lookup type here. get/set must have same type. case BKIND_NONE: // dont know what this is // fall through case BKIND_METHOD: // extracted method or dynamic data, don't know which return NULL; case BKIND_VAR: case BKIND_CONST: return traits->getTraitsBindings()->getSlotTraits(AvmCore::bindingToSlotId(b)); } } }