Arquivos
hhvm/hphp/runtime/vm/class.cpp
T
Jordan DeLong d709f1c6b1 Make fooIsPlausible functions take args by value
Also, checkTv is redundant now, since I added the refcount
assertions to tvIsPlausible earlier.
2013-07-24 10:32:26 -07:00

2444 linhas
86 KiB
C++

/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#include "hphp/runtime/base/complex_types.h"
#include "hphp/runtime/base/comparisons.h"
#include "hphp/runtime/base/hphp_array.h"
#include "hphp/util/util.h"
#include "hphp/util/debug.h"
#include "hphp/runtime/vm/jit/target-cache.h"
#include "hphp/runtime/vm/jit/translator.h"
#include "hphp/runtime/vm/treadmill.h"
#include "hphp/runtime/vm/request_arena.h"
#include "hphp/system/systemlib.h"
#include "hphp/util/logger.h"
#include "hphp/util/parser/parser.h"
#include <iostream>
#include <algorithm>
namespace HPHP {
static StringData* sd86ctor = StringData::GetStaticString("86ctor");
static StringData* sd86pinit = StringData::GetStaticString("86pinit");
static StringData* sd86sinit = StringData::GetStaticString("86sinit");
hphp_hash_map<const StringData*, const HhbcExtClassInfo*,
string_data_hash, string_data_isame> Class::s_extClassHash;
Class::InstanceCounts Class::s_instanceCounts;
ReadWriteMutex Class::s_instanceCountsLock(RankInstanceCounts);
Class::InstanceBitsMap Class::s_instanceBits;
ReadWriteMutex Class::s_instanceBitsLock(RankInstanceBits);
std::atomic<bool> Class::s_instanceBitsInit{false};
const StringData* PreClass::manglePropName(const StringData* className,
const StringData* propName,
Attr attrs) {
switch (attrs & (AttrPublic|AttrProtected|AttrPrivate)) {
case AttrPublic: {
return propName;
}
case AttrProtected: {
std::string mangledName = "";
mangledName.push_back('\0');
mangledName.push_back('*');
mangledName.push_back('\0');
mangledName += propName->data();
return StringData::GetStaticString(mangledName);
}
case AttrPrivate: {
std::string mangledName = "";
mangledName.push_back('\0');
mangledName += className->data();
mangledName.push_back('\0');
mangledName += propName->data();
return StringData::GetStaticString(mangledName);
}
default: not_reached();
}
}
//=============================================================================
// PreClass::Prop.
PreClass::Prop::Prop(PreClass* preClass,
const StringData* n,
Attr attrs,
const StringData* typeConstraint,
const StringData* docComment,
const TypedValue& val,
DataType hphpcType)
: m_preClass(preClass)
, m_name(n)
, m_attrs(attrs)
, m_typeConstraint(typeConstraint)
, m_docComment(docComment)
, m_hphpcType(hphpcType)
{
m_mangledName = manglePropName(preClass->name(), n, attrs);
memcpy(&m_val, &val, sizeof(TypedValue));
}
void PreClass::Prop::prettyPrint(std::ostream& out) const {
out << "Property ";
if (m_attrs & AttrStatic) { out << "static "; }
if (m_attrs & AttrPublic) { out << "public "; }
if (m_attrs & AttrProtected) { out << "protected "; }
if (m_attrs & AttrPrivate) { out << "private "; }
out << m_preClass->name()->data() << "::" << m_name->data() << " = ";
if (m_val.m_type == KindOfUninit) {
out << "<non-scalar>";
} else {
std::stringstream ss;
staticStreamer(&m_val, ss);
out << ss.str();
}
out << std::endl;
}
//=============================================================================
// PreClass::Const.
PreClass::Const::Const(PreClass* preClass, const StringData* n,
const StringData* typeConstraint,
const TypedValue& val, const StringData* phpCode)
: m_preClass(preClass), m_name(n), m_typeConstraint(typeConstraint),
m_phpCode(phpCode) {
memcpy(&m_val, &val, sizeof(TypedValue));
}
void PreClass::Const::prettyPrint(std::ostream& out) const {
out << "Constant " << m_preClass->name()->data() << "::" << m_name->data()
<< " = ";
if (m_val.m_type == KindOfUninit) {
out << "<non-scalar>";
} else {
std::stringstream ss;
staticStreamer(&m_val, ss);
out << ss.str();
}
out << std::endl;
}
//=============================================================================
// PreClass.
PreClass::PreClass(Unit* unit, int line1, int line2, Offset o,
const StringData* n, Attr attrs, const StringData* parent,
const StringData* docComment, Id id, Hoistable hoistable)
: m_unit(unit), m_line1(line1), m_line2(line2), m_offset(o), m_id(id),
m_builtinPropSize(0), m_attrs(attrs), m_hoistable(hoistable),
m_name(n), m_parent(parent), m_docComment(docComment),
m_InstanceCtor(nullptr) {
m_namedEntity = Unit::GetNamedEntity(n);
}
PreClass::~PreClass() {
std::for_each(methods(), methods() + numMethods(), Func::destroy);
}
void PreClass::atomicRelease() {
delete this;
}
void PreClass::prettyPrint(std::ostream &out) const {
out << "Class ";
if (m_attrs & AttrAbstract) { out << "abstract "; }
if (m_attrs & AttrFinal) { out << "final "; }
if (m_attrs & AttrInterface) { out << "interface "; }
out << m_name->data() << " at " << m_offset;
if (m_hoistable == MaybeHoistable) {
out << " (maybe-hoistable)";
} else if (m_hoistable == AlwaysHoistable) {
out << " (always-hoistable)";
}
if (m_id != -1) {
out << " (ID " << m_id << ")";
}
out << std::endl;
for (Func* const* it = methods(); it != methods() + numMethods(); ++it) {
out << " ";
(*it)->prettyPrint(out);
}
for (const Prop* it = properties();
it != properties() + numProperties();
++it) {
out << " ";
it->prettyPrint(out);
}
for (const Const* it = constants();
it != constants() + numConstants();
++it) {
out << " ";
it->prettyPrint(out);
}
}
//=============================================================================
// Class.
Class* Class::newClass(PreClass* preClass, Class* parent) {
unsigned classVecLen = (parent != nullptr) ? parent->m_classVecLen+1 : 1;
void* mem = Util::low_malloc(sizeForNClasses(classVecLen));
try {
return new (mem) Class(preClass, parent, classVecLen);
} catch (...) {
Util::low_free(mem);
throw;
}
}
Class::Class(PreClass* preClass, Class* parent, unsigned classVecLen)
: m_preClass(PreClassPtr(preClass)), m_parent(parent),
m_traitsBeginIdx(0), m_traitsEndIdx(0), m_clsInfo(nullptr),
m_builtinPropSize(0), m_classVecLen(classVecLen), m_cachedOffset(0),
m_propDataCache(-1), m_propSDataCache(-1), m_InstanceCtor(nullptr),
m_nextClass(nullptr) {
setParent();
setUsedTraits();
setMethods();
setSpecial();
setODAttributes();
setInterfaces();
setConstants();
setProperties();
setInitializers();
setClassVec();
}
Class::~Class() {
releaseRefs();
auto methods = methodRange();
while (!methods.empty()) {
Func* meth = methods.popFront();
if (meth) Func::destroy(meth);
}
}
void Class::releaseRefs() {
/*
* We have to be careful here.
* We want to free up as much as possible as early as possible, but
* some of our methods may actually belong to our parent
* This means we can't destroy *our* Funcs until our refCount
* hits zero (ie when Class::~Class gets called), because there
* could be a child class which hasn't yet been destroyed, which
* will need to inspect them. Also, we need to inspect the Funcs
* now (while we still have a references to parent) to determine
* which ones we will eventually need to free.
* Similarly, if any of our func's belong to a parent class, we
* can't free the parent, because one of our children could also
* have a reference to those func's (and its only reference to
* our parent is via this class).
*/
auto methods = mutableMethodRange();
bool okToReleaseParent = true;
while (!methods.empty()) {
Func*& meth = methods.popFront();
if (meth /* releaseRefs can be called more than once */ &&
meth->cls() != this &&
((meth->attrs() & AttrPrivate) || !meth->hasStaticLocals())) {
meth = nullptr;
okToReleaseParent = false;
}
}
if (okToReleaseParent) {
m_parent.reset();
}
m_declInterfaces.clear();
m_usedTraits.clear();
}
namespace {
class FreeClassTrigger : public Treadmill::WorkItem {
TRACE_SET_MOD(treadmill);
Class* m_cls;
public:
explicit FreeClassTrigger(Class* cls) : m_cls(cls) {
TRACE(3, "FreeClassTrigger @ %p, cls %p\n", this, m_cls);
}
void operator()() {
TRACE(3, "FreeClassTrigger: Firing @ %p , cls %p\n", this, m_cls);
if (!m_cls->decAtomicCount()) {
m_cls->atomicRelease();
}
}
};
}
void Class::destroy() {
/*
* If we were never put on NamedEntity::classList, or
* we've already been destroy'd, there's nothing to do
*/
if (!m_cachedOffset) return;
Lock l(Unit::s_classesMutex);
// Need to recheck now we have the lock
if (!m_cachedOffset) return;
// Only do this once.
m_cachedOffset = 0;
PreClass* pcls = m_preClass.get();
pcls->namedEntity()->removeClass(this);
/*
* Regardless of refCount, this Class is now unusable.
* Release what we can immediately, to allow dependent
* classes to be freed.
* Needs to be under the lock, because multiple threads
* could call destroy
*/
releaseRefs();
Treadmill::WorkItem::enqueue(new FreeClassTrigger(this));
}
void Class::atomicRelease() {
assert(!m_cachedOffset);
assert(!getCount());
this->~Class();
Util::low_free(this);
}
Class *Class::getCached() const {
return *(Class**)Transl::TargetCache::handleToPtr(m_cachedOffset);
}
void Class::setCached() {
*(Class**)Transl::TargetCache::handleToPtr(m_cachedOffset) = this;
}
bool Class::verifyPersistent() const {
if (!(attrs() & AttrPersistent)) return false;
if (m_parent.get() &&
!Transl::TargetCache::isPersistentHandle(m_parent->m_cachedOffset)) {
return false;
}
for (auto const& declInterface : m_declInterfaces) {
if (!Transl::TargetCache::isPersistentHandle(
declInterface->m_cachedOffset)) {
return false;
}
}
for (auto const& usedTrait : m_usedTraits) {
if (!Transl::TargetCache::isPersistentHandle(
usedTrait->m_cachedOffset)) {
return false;
}
}
return true;
}
const Func* Class::getDeclaredCtor() const {
const Func* f = getCtor();
return f->name() != sd86ctor ? f : nullptr;
}
void Class::initInstanceBits() {
assert(Transl::Translator::WriteLease().amOwner());
if (s_instanceBitsInit.load(std::memory_order_acquire)) return;
// First, grab a write lock on s_instanceCounts and grab the current set of
// counts as quickly as possible to minimize blocking other threads still
// trying to profile instance checks.
typedef std::pair<const StringData*, unsigned> Count;
std::vector<Count> counts;
uint64_t total = 0;
{
// If you think of the read-write lock as a shared-exclusive lock instead,
// the fact that we're grabbing a write lock to iterate over the table
// makes more sense: it's safe to concurrently modify a
// tbb::concurrent_hash_map, but iteration is not guaranteed to be safe
// with concurrent insertions.
WriteLock l(s_instanceCountsLock);
for (auto& pair : s_instanceCounts) {
counts.push_back(pair);
total += pair.second;
}
}
std::sort(counts.begin(), counts.end(), [&](const Count& a, const Count& b) {
return a.second > b.second;
});
// Next, initialize s_instanceBits with the top 127 most checked classes. Bit
// 0 is reserved as an 'initialized' flag
unsigned i = 1;
uint64_t accum = 0;
for (auto& item : counts) {
if (i >= kInstanceBits) break;
if (Class* cls = Unit::lookupUniqueClass(item.first)) {
if (!(cls->attrs() & AttrUnique)) {
continue;
}
}
s_instanceBits[item.first] = i;
accum += item.second;
++i;
}
// Print out stats about what we ended up using
if (Trace::moduleEnabledRelease(Trace::instancebits, 1)) {
Trace::traceRelease("%s: %u classes, %" PRIu64 " (%.2f%%) of warmup"
" checks\n",
__FUNCTION__, i-1, accum, 100.0 * accum / total);
if (Trace::moduleEnabledRelease(Trace::instancebits, 2)) {
accum = 0;
i = 1;
for (auto& pair : counts) {
if (i >= 256) {
Trace::traceRelease("skipping the remainder of the %zu classes\n",
counts.size());
break;
}
accum += pair.second;
Trace::traceRelease("%3u %5.2f%% %7u -- %6.2f%% %7" PRIu64 " %s\n",
i++, 100.0 * pair.second / total, pair.second,
100.0 * accum / total, accum,
pair.first->data());
}
}
}
// Finally, update m_instanceBits on every Class that currently exists. This
// must be done while holding a lock that blocks insertion of new Classes
// into their class lists, but in practice most Classes will already be
// created by now and this process takes at most 10ms.
WriteLock l(s_instanceBitsLock);
for (AllClasses ac; !ac.empty(); ) {
Class* c = ac.popFront();
c->setInstanceBitsAndParents();
}
s_instanceBitsInit.store(true, std::memory_order_release);
}
void Class::profileInstanceOf(const StringData* name) {
assert(name->isStatic());
unsigned inc = 1;
Class* c = Unit::lookupClass(name);
if (c && (c->attrs() & AttrInterface)) {
// Favor interfaces
inc = 250;
}
InstanceCounts::accessor acc;
// The extra layer of locking is here so that initInstanceBits can safely
// iterate over s_instanceCounts while building its map of names to bits.
ReadLock l(s_instanceCountsLock);
if (!s_instanceCounts.insert(acc, InstanceCounts::value_type(name, inc))) {
acc->second += inc;
}
}
bool Class::haveInstanceBit(const StringData* name) {
assert(Transl::Translator::WriteLease().amOwner());
assert(s_instanceBitsInit.load(std::memory_order_acquire));
return mapContains(s_instanceBits, name);
}
bool Class::getInstanceBitMask(const StringData* name,
int& offset, uint8_t& mask) {
assert(Transl::Translator::WriteLease().amOwner());
assert(s_instanceBitsInit.load(std::memory_order_acquire));
const size_t bitWidth = sizeof(mask) * CHAR_BIT;
unsigned bit;
if (!mapGet(s_instanceBits, name, &bit)) return false;
assert(bit >= 1 && bit < kInstanceBits);
offset = offsetof(Class, m_instanceBits) + bit / bitWidth * sizeof(mask);
mask = 1u << (bit % bitWidth);
return true;
}
/*
* Check whether a Class from a previous request is available to be defined.
* The caller should check that it has the same preClass that is being defined.
* Being available means that the parent, the interfaces and the traits are
* already defined (or become defined via autoload, if tryAutoload is true).
*
* returns Avail::True - if it is available
* Avail::Fail - if it is impossible to define the class at this point
* Avail::False- if this particular Class* cant be defined at this point
*
* Note that Fail means that at least one of the parent, interfaces and traits
* was not defined at all, while False means that at least one was defined but
* did not correspond to this Class*
*
* The parent parameter is used for two purposes: first it avoids looking up the
* active parent class for each potential Class*; and second its used on
* Fail to return the problem class so the caller can report the error
* correctly.
*/
Class::Avail Class::avail(Class*& parent, bool tryAutoload /*=false*/) const {
if (Class *ourParent = m_parent.get()) {
if (!parent) {
PreClass *ppcls = ourParent->m_preClass.get();
parent = Unit::getClass(ppcls->namedEntity(), ppcls->name(), tryAutoload);
if (!parent) {
parent = ourParent;
return Avail::Fail;
}
}
if (parent != ourParent) {
if (UNLIKELY(ourParent->isZombie())) {
const_cast<Class*>(this)->destroy();
}
return Avail::False;
}
}
for (auto const& di : m_declInterfaces) {
Class* declInterface = di.get();
PreClass *pint = declInterface->m_preClass.get();
Class* interface = Unit::getClass(pint->namedEntity(), pint->name(),
tryAutoload);
if (interface != declInterface) {
if (interface == nullptr) {
parent = declInterface;
return Avail::Fail;
}
if (UNLIKELY(declInterface->isZombie())) {
const_cast<Class*>(this)->destroy();
}
return Avail::False;
}
}
for (auto const& ut : m_usedTraits) {
Class* usedTrait = ut.get();
PreClass* ptrait = usedTrait->m_preClass.get();
Class* trait = Unit::getClass(ptrait->namedEntity(), ptrait->name(),
tryAutoload);
if (trait != usedTrait) {
if (trait == nullptr) {
parent = usedTrait;
return Avail::Fail;
}
if (UNLIKELY(usedTrait->isZombie())) {
const_cast<Class*>(this)->destroy();
}
return Avail::False;
}
}
return Avail::True;
}
void Class::initialize(TypedValue*& sProps) const {
if (m_pinitVec.size() > 0) {
if (getPropData() == nullptr) {
initProps();
}
}
// The asymmetry between the logic around initProps() above and initSProps()
// below is due to the fact that instance properties only require storage in
// g_vmContext if there are non-scalar initializers involved, whereas static
// properties *always* require storage in g_vmContext.
if (numStaticProperties() > 0) {
if ((sProps = getSPropData()) == nullptr) {
sProps = initSProps();
}
} else {
sProps = nullptr;
}
}
void Class::initialize() const {
TypedValue* sProps;
initialize(sProps);
}
Class::PropInitVec* Class::initPropsImpl() const {
assert(m_pinitVec.size() > 0);
assert(getPropData() == nullptr);
// Copy initial values for properties to a new vector that can be used to
// complete initialization for non-scalar properties via the iterative
// 86pinit() calls below. 86pinit() takes a reference to an array to populate
// with initial property values; after it completes, we copy the values into
// the new propVec.
request_arena().beginFrame();
PropInitVec* propVec = PropInitVec::allocInRequestArena(m_declPropInit);
size_t nProps = numDeclProperties();
{
Array args;
HphpArray* propArr = ArrayData::Make(nProps);
Variant arg0(propArr);
args.appendRef(arg0);
assert(propArr->getCount() == 1); // Don't want to trigger COW
{
// Create a sentinel that uniquely identifies uninitialized properties.
ObjectData* sentinel = SystemLib::AllocPinitSentinel();
sentinel->incRefCount();
TypedValue tv;
tv.m_data.pobj = sentinel;
tv.m_type = KindOfObject;
args.append(tvAsCVarRef(&tv));
sentinel->decRefCount();
}
TypedValue* tvSentinel = args->nvGetValueRef(1);
for (size_t i = 0; i < nProps; ++i) {
TypedValue& prop = (*propVec)[i];
// We have to use m_originalMangledName here because the
// 86pinit methods for traits depend on it
auto const* k = (m_declProperties[i].m_attrs & AttrPrivate)
? m_declProperties[i].m_originalMangledName
: m_declProperties[i].m_name;
// Replace undefined values with tvSentinel, which acts as a
// unique sentinel for undefined properties in 86pinit().
if (prop.m_type == KindOfUninit) {
propArr->nvInsert(const_cast<StringData*>(k), tvSentinel);
} else {
// This may seem pointless, but if you don't populate all the keys,
// you'll get "undefined index" notices in the case where a
// scalar-initialized property overrides a parent's
// non-scalar-initialized property of the same name.
propArr->nvInsert(const_cast<StringData*>(k), &prop);
}
}
try {
// Iteratively invoke 86pinit() methods upward
// through the inheritance chain.
for (Class::InitVec::const_reverse_iterator it = m_pinitVec.rbegin();
it != m_pinitVec.rend(); ++it) {
TypedValue retval;
g_vmContext->invokeFunc(&retval, *it, args, nullptr,
const_cast<Class*>(this));
assert(retval.m_type == KindOfNull);
}
} catch (...) {
// Undo the allocation of propVec
request_arena().endFrame();
throw;
}
// Pull the values out of the populated array and put them in propVec
for (size_t i = 0; i < nProps; ++i) {
TypedValue& prop = (*propVec)[i];
if (prop.m_type == KindOfUninit) {
auto const* k = (m_declProperties[i].m_attrs & AttrPrivate)
? m_declProperties[i].m_originalMangledName
: m_declProperties[i].m_name;
auto const* value = propArr->nvGet(k);
assert(value);
tvDup(*value, prop);
}
}
}
// For properties that do not require deep initialization, promote strings
// and arrays that came from 86pinit to static. This allows us to initialize
// object properties very quickly because we can just memcpy and we don't
// have to do any refcounting.
// For properties that require "deep" initialization, we have to do a little
// more work at object creation time.
Slot slot = 0;
for (PropInitVec::iterator it = propVec->begin();
it != propVec->end(); ++it, ++slot) {
TypedValueAux* tv = &(*it);
// Set deepInit if the property requires "deep" initialization.
if (m_declProperties[slot].m_attrs & AttrDeepInit) {
tv->deepInit() = true;
} else {
tvAsVariant(tv).setEvalScalar();
tv->deepInit() = false;
}
}
return propVec;
}
Slot Class::getDeclPropIndex(Class* ctx, const StringData* key,
bool& accessible) const {
Slot propInd = lookupDeclProp(key);
if (propInd != kInvalidSlot) {
Attr attrs = m_declProperties[propInd].m_attrs;
if ((attrs & (AttrProtected|AttrPrivate)) &&
!g_vmContext->getDebuggerBypassCheck()) {
// Fetch 'baseClass', which is the class in the inheritance
// tree which first declared the property
Class* baseClass = m_declProperties[propInd].m_class;
assert(baseClass);
// If ctx == baseClass, we know we have the right property
// and we can stop here.
if (ctx == baseClass) {
accessible = true;
return propInd;
}
// The anonymous context cannot access protected or private
// properties, so we can fail fast here.
if (ctx == nullptr) {
accessible = false;
return propInd;
}
assert(ctx);
if (attrs & AttrPrivate) {
// ctx != baseClass and the property is private, so it is not
// accessible. We need to keep going because ctx may define a
// private property with this name.
accessible = false;
} else {
if (ctx->classof(baseClass)) {
// ctx is derived from baseClass, so we know this protected
// property is accessible and we know ctx cannot have private
// property with the same name, so we're done.
accessible = true;
return propInd;
}
if (!baseClass->classof(ctx)) {
// ctx is not the same, an ancestor, or a descendent of baseClass,
// so the property is not accessible. Also, we know that ctx cannot
// be the same or an ancestor of this, so we don't need to check if
// ctx declares a private property with the same name and we can
// fail fast here.
accessible = false;
return propInd;
}
// We now know this protected property is accessible, but we need to
// keep going because ctx may define a private property with the same
// name.
accessible = true;
assert(baseClass->classof(ctx));
}
} else {
// The property is public (or we're in the debugger and we are bypassing
// accessibility checks).
accessible = true;
// If ctx == this, we don't have to check if ctx defines a private
// property with the same name and we can stop here.
if (ctx == this) {
return propInd;
}
// We still need to check if ctx defines a private property with the
// same name.
}
} else {
// We didn't find a visible declared property in this's property map
accessible = false;
}
// If ctx is an ancestor of this, check if ctx has a private property
// with the same name.
if (ctx && classof(ctx)) {
Slot ctxPropInd = ctx->lookupDeclProp(key);
if (ctxPropInd != kInvalidSlot &&
ctx->m_declProperties[ctxPropInd].m_class == ctx &&
(ctx->m_declProperties[ctxPropInd].m_attrs & AttrPrivate)) {
// A private property from ctx trumps any other property we may
// have found.
accessible = true;
return ctxPropInd;
}
}
return propInd;
}
TypedValue* Class::initSPropsImpl() const {
assert(numStaticProperties() > 0);
assert(getSPropData() == nullptr);
// Create an array that is initially large enough to hold all static
// properties.
TypedValue* const spropTable =
new (request_arena()) TypedValue[m_staticProperties.size()];
const bool hasNonscalarInit = !m_sinitVec.empty();
Array propArr;
TypedValue tvSentinel;
tvWriteUninit(&tvSentinel);
SCOPE_EXIT {
tvRefcountedDecRef(&tvSentinel);
};
// If there are non-scalar initializers (i.e. 86sinit methods), run them now.
// They'll put their initialized values into an array, and we'll read any
// values we need out of the array later.
if (hasNonscalarInit) {
HphpArray* propData = ArrayData::Make(m_staticProperties.size());
Variant arg0(propData);
// The 86sinit functions will initialize some subset of the static props.
// Set all of them to a sentinel object so we can distinguish these.
tvSentinel.m_type = KindOfObject;
tvSentinel.m_data.pobj = SystemLib::AllocPinitSentinel();
tvSentinel.m_data.pobj->incRefCount();
for (Slot slot = 0; slot < m_staticProperties.size(); ++slot) {
auto const& sProp = m_staticProperties[slot];
propData->set(const_cast<StringData*>(sProp.m_name),
tvAsCVarRef(&tvSentinel),
false);
}
// Run the 86sinit functions, going up the inheritance chain.
Array args;
args.appendRef(arg0);
assert(propData->getCount() == 1); // don't want to trigger COW
for (unsigned i = 0; i < m_sinitVec.size(); i++) {
TypedValue retval;
g_vmContext->invokeFunc(&retval, m_sinitVec[i], args, nullptr,
const_cast<Class*>(this));
assert(retval.m_type == KindOfNull);
}
// Transfer ownership of the reference to the outer scope.
propArr = propData;
}
// A helper to look up values produced by 86sinit.
auto getValueFromArr = [&](const StringData* name) -> const TypedValue* {
if (!propArr.isNull()) {
assert(tvSentinel.m_type == KindOfObject);
auto const* v = propArr.get()->nvGet(name);
if (v->m_type != KindOfObject ||
v->m_data.pobj != tvSentinel.m_data.pobj) {
return v;
}
}
return nullptr;
};
for (Slot slot = 0; slot < m_staticProperties.size(); ++slot) {
auto const& sProp = m_staticProperties[slot];
auto const* propName = sProp.m_name;
if (sProp.m_class == this) {
auto const* value = getValueFromArr(propName);
if (value) {
tvDup(*value, spropTable[slot]);
} else {
assert(tvIsStatic(&sProp.m_val));
spropTable[slot] = sProp.m_val;
}
} else {
bool visible, accessible;
auto* storage = sProp.m_class->getSProp(nullptr, propName,
visible, accessible);
auto const* value = getValueFromArr(propName);
if (value) {
tvDup(*value, *storage);
}
tvBindIndirect(&spropTable[slot], storage);
}
}
return spropTable;
}
TypedValue* Class::getSProp(Class* ctx, const StringData* sPropName,
bool& visible, bool& accessible) const {
TypedValue* sProps;
initialize(sProps);
Slot sPropInd = lookupSProp(sPropName);
if (sPropInd == kInvalidSlot) {
// Non-existant property.
visible = false;
accessible = false;
return nullptr;
}
visible = true;
if (ctx == this) {
// Property access is from within a method of this class, so the property
// is accessible.
accessible = true;
} else {
Attr sPropAttrs = m_staticProperties[sPropInd].m_attrs;
if ((ctx != nullptr) && (classof(ctx) || ctx->classof(this))) {
// Property access is from within a parent class's method, which is
// allowed for protected/public properties.
switch (sPropAttrs & (AttrPublic|AttrProtected|AttrPrivate)) {
case AttrPublic:
case AttrProtected: accessible = true; break;
case AttrPrivate:
accessible = g_vmContext->getDebuggerBypassCheck(); break;
default: not_reached();
}
} else {
// Property access is in an effectively anonymous context, so only public
// properties are accessible.
switch (sPropAttrs & (AttrPublic|AttrProtected|AttrPrivate)) {
case AttrPublic: accessible = true; break;
case AttrProtected:
case AttrPrivate:
accessible = g_vmContext->getDebuggerBypassCheck(); break;
default: not_reached();
}
}
}
assert(sProps != nullptr);
TypedValue* sProp = tvDerefIndirect(&sProps[sPropInd]);
assert(sProp->m_type != KindOfUninit &&
"static property initialization failed to initialize a property");
return sProp;
}
bool Class::IsPropAccessible(const Prop& prop, Class* ctx) {
if (prop.m_attrs & AttrPublic) return true;
if (prop.m_attrs & AttrPrivate) return prop.m_class == ctx;
if (!ctx) return false;
return prop.m_class->classof(ctx) || ctx->classof(prop.m_class);
}
TypedValue Class::getStaticPropInitVal(const SProp& prop) {
Class* declCls = prop.m_class;
Slot s = declCls->m_staticProperties.findIndex(prop.m_name);
assert(s != kInvalidSlot);
return declCls->m_staticProperties[s].m_val;
}
HphpArray* Class::initClsCnsData() const {
Slot nConstants = m_constants.size();
HphpArray* constants = ArrayData::Make(nConstants);
constants->incRefCount();
if (m_parent.get() != nullptr) {
if (g_vmContext->getClsCnsData(m_parent.get()) == nullptr) {
// Initialize recursively up the inheritance chain.
m_parent->initClsCnsData();
}
}
for (Slot i = 0; i < nConstants; ++i) {
const Const& constant = m_constants[i];
const TypedValue* tv = &constant.m_val;
constants->set((StringData*)constant.m_name, tvAsCVarRef(tv), false);
// XXX: set() converts KindOfUninit to KindOfNull, but our class
// constant logic needs to store KindOfUninit to indicate the the
// constant's value has not been computed yet. We should find a better
// way to deal with this.
if (tv->m_type == KindOfUninit) {
constants->nvGetValueRef(i)->m_type = KindOfUninit;
}
}
g_vmContext->setClsCnsData(this, constants);
return constants;
}
Cell* Class::cnsNameToTV(const StringData* clsCnsName,
Slot& clsCnsInd) const {
clsCnsInd = m_constants.findIndex(clsCnsName);
if (clsCnsInd == kInvalidSlot) {
return nullptr;
}
auto const ret = const_cast<Cell*>(&m_constants[clsCnsInd].m_val);
assert(cellIsPlausible(*ret));
return ret;
}
Cell* Class::clsCnsGet(const StringData* clsCnsName) const {
Slot clsCnsInd;
Cell* clsCns = cnsNameToTV(clsCnsName, clsCnsInd);
if (!clsCns || clsCns->m_type != KindOfUninit) {
return clsCns;
}
// This constant has a non-scalar initializer, so look in g_vmContext for
// an entry associated with this class.
HphpArray* clsCnsData = g_vmContext->getClsCnsData(this);
if (clsCnsData == nullptr) {
clsCnsData = initClsCnsData();
}
clsCns = clsCnsData->nvGetValueRef(clsCnsInd);
if (clsCns->m_type == KindOfUninit) {
// The class constant has not been initialized yet; do so.
static StringData* sd86cinit = StringData::GetStaticString("86cinit");
const Func* meth86cinit =
m_constants[clsCnsInd].m_class->lookupMethod(sd86cinit);
TypedValue tv[1];
tv->m_data.pstr = (StringData*)clsCnsName;
tv->m_type = KindOfString;
clsCnsName->incRefCount();
g_vmContext->invokeFuncFew(clsCns, meth86cinit, ActRec::encodeClass(this),
nullptr, 1, tv);
}
assert(cellIsPlausible(*clsCns));
return clsCns;
}
DataType Class::clsCnsType(const StringData* cnsName) const {
Slot slot;
auto const cns = cnsNameToTV(cnsName, slot);
// TODO: lookup the constant in target cache in case it's dynamic
// and already initialized.
if (!cns) return KindOfUninit;
return cns->m_type;
}
void Class::setParent() {
// Validate the parent
if (m_parent.get() != nullptr) {
Attr attrs = m_parent->attrs();
if (UNLIKELY(attrs & (AttrFinal | AttrInterface | AttrTrait))) {
static StringData* sd___MockClass =
StringData::GetStaticString("__MockClass");
if (!(attrs & AttrFinal) ||
m_preClass->userAttributes().find(sd___MockClass) ==
m_preClass->userAttributes().end()) {
raise_error("Class %s may not inherit from %s (%s)",
m_preClass->name()->data(),
((attrs & AttrFinal) ? "final class" :
(attrs & AttrInterface) ? "interface" : "trait"),
m_parent->name()->data());
}
}
}
// Cache m_preClass->attrs()
m_attrCopy = m_preClass->attrs();
// Handle stuff specific to cppext classes
if (m_preClass->instanceCtor()) {
m_InstanceCtor = m_preClass->instanceCtor();
m_builtinPropSize = m_preClass->builtinPropSize();
m_clsInfo = ClassInfo::FindSystemClassInterfaceOrTrait(nameRef());
} else if (m_parent.get()) {
m_InstanceCtor = m_parent->m_InstanceCtor;
m_builtinPropSize = m_parent->m_builtinPropSize;
}
}
static Func* findSpecialMethod(Class* cls, const StringData* name) {
if (!cls->preClass()->hasMethod(name)) return nullptr;
Func* f = cls->preClass()->lookupMethod(name);
f = f->clone();
f->setNewFuncId();
f->setCls(cls);
f->setBaseCls(cls);
f->setHasPrivateAncestor(false);
return f;
}
void Class::setSpecial() {
static StringData* sd_toString = StringData::GetStaticString("__toString");
static StringData* sd_uuconstruct =
StringData::GetStaticString("__construct");
static StringData* sd_uudestruct =
StringData::GetStaticString("__destruct");
m_toString = lookupMethod(sd_toString);
m_dtor = lookupMethod(sd_uudestruct);
// Look for __construct() declared in either this class or a trait
Func* fConstruct = lookupMethod(sd_uuconstruct);
if (fConstruct && (fConstruct->preClass() == m_preClass.get() ||
fConstruct->preClass()->attrs() & AttrTrait)) {
m_ctor = fConstruct;
return;
}
if (!(attrs() & AttrTrait)) {
// Look for Foo::Foo() declared in this class (cannot be via trait).
Func* fNamedCtor = lookupMethod(m_preClass->name());
if (fNamedCtor && fNamedCtor->preClass() == m_preClass.get() &&
!(fNamedCtor->attrs() & AttrTrait)) {
/*
Note: AttrTrait was set by the emitter if hphpc inlined a trait
method into a class (WholeProgram mode only), so that we dont
accidently mark it as a constructor here
*/
m_ctor = fNamedCtor;
return;
}
}
// Look for parent constructor other than 86ctor().
if (m_parent.get() != nullptr &&
m_parent->m_ctor->name() != sd86ctor) {
m_ctor = m_parent->m_ctor;
return;
}
// Use 86ctor(), since no program-supplied constructor exists
m_ctor = findSpecialMethod(this, sd86ctor);
assert(m_ctor && "class had no user-defined constructor or 86ctor");
assert((m_ctor->attrs() & ~AttrBuiltin) ==
(AttrPublic|AttrNoInjection|AttrPhpLeafFn));
}
void Class::applyTraitPrecRule(const PreClass::TraitPrecRule& rule,
MethodToTraitListMap& importMethToTraitMap) {
const StringData* methName = rule.getMethodName();
const StringData* selectedTraitName = rule.getSelectedTraitName();
TraitNameSet otherTraitNames;
rule.getOtherTraitNames(otherTraitNames);
auto methIter = importMethToTraitMap.find(methName);
if (methIter == importMethToTraitMap.end()) {
raise_error("unknown method '%s'", methName->data());
}
bool foundSelectedTrait = false;
TraitMethodList &methList = methIter->second;
for (TraitMethodList::iterator nextTraitIter = methList.begin();
nextTraitIter != methList.end(); ) {
TraitMethodList::iterator traitIter = nextTraitIter++;
const StringData* availTraitName = traitIter->m_trait->name();
if (availTraitName == selectedTraitName) {
foundSelectedTrait = true;
} else {
if (otherTraitNames.find(availTraitName) != otherTraitNames.end()) {
otherTraitNames.erase(availTraitName);
methList.erase(traitIter);
}
}
}
// Check error conditions
if (!foundSelectedTrait) {
raise_error("unknown trait '%s'", selectedTraitName->data());
}
if (otherTraitNames.size()) {
raise_error("unknown trait '%s'", (*otherTraitNames.begin())->data());
}
}
Class* Class::findSingleTraitWithMethod(const StringData* methName) {
// Note: m_methods includes methods from parents / traits recursively
Class* traitCls = nullptr;
for (auto const& t : m_usedTraits) {
if (t->m_methods.contains(methName)) {
if (traitCls != nullptr) { // more than one trait contains method
return nullptr;
}
traitCls = t.get();
}
}
return traitCls;
}
void Class::setImportTraitMethodModifiers(TraitMethodList& methList,
Class* traitCls,
Attr modifiers) {
for (TraitMethodList::iterator iter = methList.begin();
iter != methList.end(); iter++) {
if (iter->m_trait == traitCls) {
iter->m_modifiers = modifiers;
return;
}
}
}
// Keep track of trait aliases in the class to support
// ReflectionClass::getTraitAliases
void Class::addTraitAlias(const StringData* traitName,
const StringData* origMethName,
const StringData* newMethName) {
char buf[traitName->size() + origMethName->size() + 9];
sprintf(buf, "%s::%s", (traitName->empty() ? "(null)" : traitName->data()),
origMethName->data());
const StringData* origName = StringData::GetStaticString(buf);
m_traitAliases.push_back(std::pair<const StringData*, const StringData*>
(newMethName, origName));
}
void Class::applyTraitAliasRule(const PreClass::TraitAliasRule& rule,
MethodToTraitListMap& importMethToTraitMap) {
const StringData* traitName = rule.getTraitName();
const StringData* origMethName = rule.getOrigMethodName();
const StringData* newMethName = rule.getNewMethodName();
Class* traitCls = nullptr;
if (traitName->empty()) {
traitCls = findSingleTraitWithMethod(origMethName);
} else {
traitCls = Unit::loadClass(traitName);
}
if (!traitCls || (!(traitCls->attrs() & AttrTrait))) {
raise_error("unknown trait '%s'", traitName->data());
}
// Save info to support ReflectionClass::getTraitAliases
addTraitAlias(traitName, origMethName, newMethName);
Func* traitMeth = traitCls->lookupMethod(origMethName);
if (!traitMeth) {
raise_error("unknown trait method '%s'", origMethName->data());
}
Attr ruleModifiers;
if (origMethName == newMethName) {
ruleModifiers = rule.getModifiers();
setImportTraitMethodModifiers(importMethToTraitMap[origMethName],
traitCls, ruleModifiers);
} else {
ruleModifiers = rule.getModifiers();
TraitMethod traitMethod(traitCls, traitMeth, ruleModifiers);
if (!Func::isSpecial(newMethName)) {
importMethToTraitMap[newMethName].push_back(traitMethod);
}
}
if (ruleModifiers & AttrStatic) {
raise_error("cannot use 'static' as access modifier");
}
}
void Class::applyTraitRules(MethodToTraitListMap& importMethToTraitMap) {
for (size_t i = 0; i < m_preClass->traitPrecRules().size(); i++) {
applyTraitPrecRule(m_preClass->traitPrecRules()[i],
importMethToTraitMap);
}
for (size_t i = 0; i < m_preClass->traitAliasRules().size(); i++) {
applyTraitAliasRule(m_preClass->traitAliasRules()[i],
importMethToTraitMap);
}
}
void Class::importTraitMethod(const TraitMethod& traitMethod,
const StringData* methName,
MethodMap::Builder& builder) {
Func* method = traitMethod.m_method;
Attr modifiers = traitMethod.m_modifiers;
MethodMap::Builder::iterator mm_iter = builder.find(methName);
// For abstract methods, simply return if method already declared
if ((modifiers & AttrAbstract) && mm_iter != builder.end()) {
return;
}
if (modifiers == AttrNone) {
modifiers = method->attrs();
} else {
// Trait alias statements are only allowed to change the attributes that
// are part 'attrMask' below; all other method attributes are preserved
Attr attrMask = (Attr)(AttrPublic | AttrProtected | AttrPrivate |
AttrAbstract | AttrFinal);
modifiers = (Attr)((modifiers & (attrMask)) |
(method->attrs() & ~(attrMask)));
}
Func* parentMethod = nullptr;
if (mm_iter != builder.end()) {
Func* existingMethod = builder[mm_iter->second];
if (existingMethod->cls() == this) {
// Don't override an existing method if this class provided an
// implementation
return;
}
parentMethod = existingMethod;
}
Func* f = method->clone();
f->setNewFuncId();
f->setClsAndName(this, methName);
f->setAttrs(modifiers);
if (!parentMethod) {
// New method
builder.add(methName, f);
f->setBaseCls(this);
f->setHasPrivateAncestor(false);
} else {
// Override an existing method
Class* baseClass;
methodOverrideCheck(parentMethod, f);
assert(!(f->attrs() & AttrPrivate) ||
(parentMethod->attrs() & AttrPrivate));
if ((parentMethod->attrs() & AttrPrivate) || (f->attrs() & AttrPrivate)) {
baseClass = this;
} else {
baseClass = parentMethod->baseCls();
}
f->setBaseCls(baseClass);
f->setHasPrivateAncestor(
parentMethod->hasPrivateAncestor() ||
(parentMethod->attrs() & AttrPrivate));
builder[mm_iter->second] = f;
}
}
// This method removes trait abstract methods that are either:
// 1) implemented by other traits
// 2) duplicate
void Class::removeSpareTraitAbstractMethods(
MethodToTraitListMap& importMethToTraitMap) {
for (MethodToTraitListMap::iterator iter = importMethToTraitMap.begin();
iter != importMethToTraitMap.end(); iter++) {
TraitMethodList& tMethList = iter->second;
bool hasNonAbstractMeth = false;
unsigned countAbstractMeths = 0;
for (TraitMethodList::const_iterator traitMethIter = tMethList.begin();
traitMethIter != tMethList.end(); traitMethIter++) {
if (!(traitMethIter->m_modifiers & AttrAbstract)) {
hasNonAbstractMeth = true;
} else {
countAbstractMeths++;
}
}
if (hasNonAbstractMeth || countAbstractMeths > 1) {
// Erase spare abstract declarations
bool firstAbstractMeth = true;
for (TraitMethodList::iterator nextTraitIter = tMethList.begin();
nextTraitIter != tMethList.end(); ) {
TraitMethodList::iterator traitIter = nextTraitIter++;
if (traitIter->m_modifiers & AttrAbstract) {
if (hasNonAbstractMeth || !firstAbstractMeth) {
tMethList.erase(traitIter);
}
firstAbstractMeth = false;
}
}
}
}
}
// fatals on error
void Class::importTraitMethods(MethodMap::Builder& builder) {
MethodToTraitListMap importMethToTraitMap;
// 1. Find all methods to be imported
for (auto const& t : m_usedTraits) {
Class* trait = t.get();
for (Slot i = 0; i < trait->m_methods.size(); ++i) {
Func* method = trait->m_methods[i];
const StringData* methName = method->name();
TraitMethod traitMethod(trait, method, method->attrs());
if (!Func::isSpecial(methName)) {
importMethToTraitMap[methName].push_back(traitMethod);
}
}
}
// 2. Apply trait rules
applyTraitRules(importMethToTraitMap);
// 3. Remove abstract methods provided by other traits, and also duplicates
removeSpareTraitAbstractMethods(importMethToTraitMap);
// 4. Actually import the methods
for (MethodToTraitListMap::const_iterator iter =
importMethToTraitMap.begin();
iter != importMethToTraitMap.end(); iter++) {
// The rules may rule out a method from all traits.
// In this case, simply don't import the method.
if (iter->second.size() == 0) {
continue;
}
// Consistency checking: each name must only refer to one imported method
if (iter->second.size() > 1) {
// OK if the class will override the method...
if (m_preClass->hasMethod(iter->first)) continue;
raise_error("method '%s' declared in multiple traits",
iter->first->data());
}
TraitMethodList::const_iterator traitMethIter = iter->second.begin();
importTraitMethod(*traitMethIter, iter->first, builder);
}
}
void Class::methodOverrideCheck(const Func* parentMethod, const Func* method) {
// Skip special methods
if (method->isGenerated()) return;
if ((parentMethod->attrs() & AttrFinal)) {
static StringData* sd___MockClass =
StringData::GetStaticString("__MockClass");
if (m_preClass->userAttributes().find(sd___MockClass) ==
m_preClass->userAttributes().end()) {
raise_error("Cannot override final method %s::%s()",
m_parent->name()->data(), parentMethod->name()->data());
}
}
if (method->attrs() & AttrAbstract) {
raise_error("Cannot re-declare %sabstract method %s::%s() abstract in "
"class %s",
(parentMethod->attrs() & AttrAbstract) ? "" : "non-",
m_parent->m_preClass->name()->data(),
parentMethod->name()->data(), m_preClass->name()->data());
}
if ((method->attrs() & (AttrPublic | AttrProtected | AttrPrivate)) >
(parentMethod->attrs() & (AttrPublic | AttrProtected | AttrPrivate))) {
raise_error(
"Access level to %s::%s() must be %s (as in class %s) or weaker",
m_preClass->name()->data(), method->name()->data(),
attrToVisibilityStr(parentMethod->attrs()),
m_parent->name()->data());
}
if ((method->attrs() & AttrStatic) != (parentMethod->attrs() & AttrStatic)) {
raise_error("Cannot change %sstatic method %s::%s() to %sstatic in %s",
(parentMethod->attrs() & AttrStatic) ? "" : "non-",
parentMethod->baseCls()->name()->data(),
method->name()->data(),
(method->attrs() & AttrStatic) ? "" : "non-",
m_preClass->name()->data());
}
Func* baseMethod = parentMethod->baseCls()->lookupMethod(method->name());
if (!(method->attrs() & AttrAbstract) &&
(baseMethod->attrs() & AttrAbstract) &&
(!hphpiCompat || strcmp(method->name()->data(), "__construct"))) {
method->parametersCompat(m_preClass.get(), baseMethod);
}
}
void Class::setMethods() {
std::vector<Slot> parentMethodsWithStaticLocals;
MethodMap::Builder builder;
if (m_parent.get() != nullptr) {
// Copy down the parent's method entries. These may be overridden below.
for (Slot i = 0; i < m_parent->m_methods.size(); ++i) {
Func* f = m_parent->m_methods[i];
assert(f);
if ((f->attrs() & AttrClone) ||
(!(f->attrs() & AttrPrivate) && f->hasStaticLocals())) {
// When copying down an entry for a non-private method that has
// static locals, we want to make a copy of the Func so that it
// gets a distinct set of static locals variables. We defer making
// a copy of the parent method until the end because it might get
// overriden below.
parentMethodsWithStaticLocals.push_back(i);
}
assert(builder.size() == i);
builder.add(f->name(), f);
}
}
assert(AttrPublic < AttrProtected && AttrProtected < AttrPrivate);
// Overlay/append this class's public/protected methods onto/to those of the
// parent.
for (size_t methI = 0; methI < m_preClass->numMethods(); ++methI) {
Func* method = m_preClass->methods()[methI];
if (Func::isSpecial(method->name())) {
if (method->name() == sd86ctor ||
method->name() == sd86sinit ||
method->name() == sd86pinit) {
/*
* we could also skip the cinit function here, but
* that would mean storing it somewhere else.
*/
continue;
}
}
MethodMap::Builder::iterator it2 = builder.find(method->name());
if (it2 != builder.end()) {
Func* parentMethod = builder[it2->second];
// We should never have null func pointers to deal with
assert(parentMethod);
methodOverrideCheck(parentMethod, method);
// Overlay.
Func* f = method->clone();
f->setNewFuncId();
f->setCls(this);
Class* baseClass;
assert(!(f->attrs() & AttrPrivate) ||
(parentMethod->attrs() & AttrPrivate));
if ((parentMethod->attrs() & AttrPrivate) || (f->attrs() & AttrPrivate)) {
baseClass = this;
} else {
baseClass = parentMethod->baseCls();
}
f->setBaseCls(baseClass);
f->setHasPrivateAncestor(
parentMethod->hasPrivateAncestor() ||
(parentMethod->attrs() & AttrPrivate));
builder[it2->second] = f;
} else {
// This is the first class that declares the method
Class* baseClass = this;
// Append.
Func* f = method->clone();
f->setNewFuncId();
f->setCls(this);
f->setBaseCls(baseClass);
f->setHasPrivateAncestor(false);
builder.add(method->name(), f);
}
}
m_traitsBeginIdx = builder.size();
if (m_usedTraits.size()) {
importTraitMethods(builder);
}
m_traitsEndIdx = builder.size();
// Make copies of Funcs inherited from the parent class that have
// static locals
std::vector<Slot>::const_iterator it;
for (it = parentMethodsWithStaticLocals.begin();
it != parentMethodsWithStaticLocals.end(); ++it) {
Func*& f = builder[*it];
if (f->cls() != this) {
// Don't update f's m_cls if it doesn't have AttrClone set:
// we're cloning it so that we get a distinct set of static
// locals and a separate translation, not a different context
// class.
f = f->clone();
if (f->attrs() & AttrClone) {
f->setCls(this);
}
f->setNewFuncId();
}
}
// If class is not abstract, check that all abstract methods have been defined
if (!(attrs() & (AttrTrait | AttrInterface | AttrAbstract))) {
for (Slot i = 0; i < builder.size(); i++) {
const Func* meth = builder[i];
if (meth->attrs() & AttrAbstract) {
raise_error("Class %s contains abstract method (%s) and "
"must therefore be declared abstract or implement "
"the remaining methods", m_preClass->name()->data(),
meth->name()->data());
}
}
}
m_methods.create(builder);
for (Slot i = 0; i < m_methods.size(); ++i) {
m_methods[i]->setMethodSlot(i);
}
}
void Class::setODAttributes() {
static StringData* sd__sleep = StringData::GetStaticString("__sleep");
static StringData* sd__get = StringData::GetStaticString("__get");
static StringData* sd__set = StringData::GetStaticString("__set");
static StringData* sd__isset = StringData::GetStaticString("__isset");
static StringData* sd__unset = StringData::GetStaticString("__unset");
static StringData* sd__call = StringData::GetStaticString("__call");
static StringData* sd__callStatic
= StringData::GetStaticString("__callStatic");
m_ODAttrs = 0;
if (lookupMethod(sd__sleep )) { m_ODAttrs |= ObjectData::HasSleep; }
if (lookupMethod(sd__get )) { m_ODAttrs |= ObjectData::UseGet; }
if (lookupMethod(sd__set )) { m_ODAttrs |= ObjectData::UseSet; }
if (lookupMethod(sd__isset )) { m_ODAttrs |= ObjectData::UseIsset; }
if (lookupMethod(sd__unset )) { m_ODAttrs |= ObjectData::UseUnset; }
if (lookupMethod(sd__call )) { m_ODAttrs |= ObjectData::HasCall; }
if (lookupMethod(sd__callStatic)) { m_ODAttrs |= ObjectData::HasCallStatic; }
}
void Class::setConstants() {
ConstMap::Builder builder;
if (m_parent.get() != nullptr) {
for (Slot i = 0; i < m_parent->m_constants.size(); ++i) {
// Copy parent's constants.
builder.add(m_parent->m_constants[i].m_name, m_parent->m_constants[i]);
}
}
// Copy in interface constants.
for (auto it = m_declInterfaces.begin(); it != m_declInterfaces.end(); ++it) {
for (Slot slot = 0; slot < (*it)->m_constants.size(); ++slot) {
const Const& iConst = (*it)->m_constants[slot];
// If you're inheriting a constant with the same name as an
// existing one, they must originate from the same place.
ConstMap::Builder::iterator existing = builder.find(iConst.m_name);
if (existing != builder.end() &&
builder[existing->second].m_class != iConst.m_class) {
raise_error("Cannot inherit previously-inherited constant %s",
iConst.m_name->data());
}
builder.add(iConst.m_name, iConst);
}
}
for (Slot i = 0, sz = m_preClass->numConstants(); i < sz; ++i) {
const PreClass::Const* preConst = &m_preClass->constants()[i];
ConstMap::Builder::iterator it2 = builder.find(preConst->name());
if (it2 != builder.end()) {
if (!(builder[it2->second].m_class->attrs() & AttrInterface)) {
// Overlay ancestor's constant, only if it was not an interface const.
builder[it2->second].m_class = this;
builder[it2->second].m_val = preConst->val();
} else {
raise_error("Cannot override previously defined constant %s::%s in %s",
builder[it2->second].m_class->name()->data(),
preConst->name()->data(),
m_preClass->name()->data());
}
} else {
// Append constant.
Const constant;
constant.m_class = this;
constant.m_name = preConst->name();
constant.m_val = preConst->val();
constant.m_phpCode = preConst->phpCode();
builder.add(preConst->name(), constant);
}
}
m_constants.create(builder);
}
static void copyDeepInitAttr(const PreClass::Prop* pclsProp,
Class::Prop* clsProp) {
if (pclsProp->attrs() & AttrDeepInit) {
clsProp->m_attrs = (Attr)(clsProp->m_attrs | AttrDeepInit);
} else {
clsProp->m_attrs = (Attr)(clsProp->m_attrs & ~AttrDeepInit);
}
}
void Class::setProperties() {
int numInaccessible = 0;
PropMap::Builder curPropMap;
SPropMap::Builder curSPropMap;
m_hasDeepInitProps = false;
if (m_parent.get() != nullptr) {
// m_hasDeepInitProps indicates if there are properties that require
// deep initialization. Note there are cases where m_hasDeepInitProps is
// true but none of the properties require deep initialization; this can
// happen if a derived class redeclares a public or protected property
// from an ancestor class. We still get correct behavior in these cases,
// so it works out okay.
m_hasDeepInitProps = m_parent->m_hasDeepInitProps;
for (Slot slot = 0; slot < m_parent->m_declProperties.size(); ++slot) {
const Prop& parentProp = m_parent->m_declProperties[slot];
// Copy parent's declared property. Protected properties may be
// weakened to public below, but otherwise, the parent's properties
// will stay the same for this class.
Prop prop;
prop.m_class = parentProp.m_class;
prop.m_mangledName = parentProp.m_mangledName;
prop.m_originalMangledName = parentProp.m_originalMangledName;
prop.m_attrs = parentProp.m_attrs;
prop.m_docComment = parentProp.m_docComment;
prop.m_typeConstraint = parentProp.m_typeConstraint;
prop.m_name = parentProp.m_name;
prop.m_hphpcType = parentProp.m_hphpcType;
if (!(parentProp.m_attrs & AttrPrivate)) {
curPropMap.add(prop.m_name, prop);
} else {
++numInaccessible;
curPropMap.addUnnamed(prop);
}
}
m_declPropInit = m_parent->m_declPropInit;
for (Slot slot = 0; slot < m_parent->m_staticProperties.size(); ++slot) {
const SProp& parentProp = m_parent->m_staticProperties[slot];
if (parentProp.m_attrs & AttrPrivate) continue;
// Alias parent's static property.
SProp sProp;
sProp.m_name = parentProp.m_name;
sProp.m_attrs = parentProp.m_attrs;
sProp.m_typeConstraint = parentProp.m_typeConstraint;
sProp.m_docComment = parentProp.m_docComment;
sProp.m_class = parentProp.m_class;
tvWriteUninit(&sProp.m_val);
curSPropMap.add(sProp.m_name, sProp);
}
}
assert(AttrPublic < AttrProtected && AttrProtected < AttrPrivate);
for (Slot slot = 0; slot < m_preClass->numProperties(); ++slot) {
const PreClass::Prop* preProp = &m_preClass->properties()[slot];
if (!(preProp->attrs() & AttrStatic)) {
// Overlay/append this class's protected and public properties onto/to
// those of the parent, and append this class's private properties.
// Append order doesn't matter here (unlike in setMethods()).
// Prohibit static-->non-static redeclaration.
SPropMap::Builder::iterator it2 = curSPropMap.find(preProp->name());
if (it2 != curSPropMap.end()) {
raise_error("Cannot redeclare static %s::$%s as non-static %s::$%s",
curSPropMap[it2->second].m_class->name()->data(),
preProp->name()->data(), m_preClass->name()->data(),
preProp->name()->data());
}
// Get parent's equivalent property, if one exists.
const Prop* parentProp = nullptr;
if (m_parent.get() != nullptr) {
Slot id = m_parent->m_declProperties.findIndex(preProp->name());
if (id != kInvalidSlot) {
parentProp = &m_parent->m_declProperties[id];
}
}
// Prohibit strengthening.
if (parentProp
&& (preProp->attrs() & (AttrPublic|AttrProtected|AttrPrivate))
> (parentProp->m_attrs & (AttrPublic|AttrProtected|AttrPrivate))) {
raise_error(
"Access level to %s::$%s() must be %s (as in class %s) or weaker",
m_preClass->name()->data(), preProp->name()->data(),
attrToVisibilityStr(parentProp->m_attrs),
m_parent->name()->data());
}
if (preProp->attrs() & AttrDeepInit) {
m_hasDeepInitProps = true;
}
switch (preProp->attrs() & (AttrPublic|AttrProtected|AttrPrivate)) {
case AttrPrivate: {
// Append a new private property.
Prop prop;
prop.m_name = preProp->name();
prop.m_mangledName = preProp->mangledName();
prop.m_originalMangledName = preProp->mangledName();
prop.m_attrs = preProp->attrs();
// This is the first class to declare this property
prop.m_class = this;
prop.m_typeConstraint = preProp->typeConstraint();
prop.m_docComment = preProp->docComment();
prop.m_hphpcType = preProp->hphpcType();
curPropMap.add(preProp->name(), prop);
m_declPropInit.push_back(m_preClass->lookupProp(preProp->name())
->val());
break;
}
case AttrProtected: {
// Check whether a superclass has already declared this protected
// property.
PropMap::Builder::iterator it2 = curPropMap.find(preProp->name());
if (it2 != curPropMap.end()) {
assert((curPropMap[it2->second].m_attrs
& (AttrPublic|AttrProtected|AttrPrivate)) == AttrProtected);
const TypedValue& tv = m_preClass->lookupProp(preProp->name())->val();
TypedValueAux& tvaux = m_declPropInit[it2->second];
tvaux.m_data = tv.m_data;
tvaux.m_type = tv.m_type;
copyDeepInitAttr(preProp, &curPropMap[it2->second]);
break;
}
// Append a new protected property.
Prop prop;
prop.m_name = preProp->name();
prop.m_mangledName = preProp->mangledName();
prop.m_originalMangledName = preProp->mangledName();
prop.m_attrs = preProp->attrs();
prop.m_typeConstraint = preProp->typeConstraint();
// This is the first class to declare this property
prop.m_class = this;
prop.m_docComment = preProp->docComment();
prop.m_hphpcType = preProp->hphpcType();
curPropMap.add(preProp->name(), prop);
m_declPropInit.push_back(m_preClass->lookupProp(preProp->name())
->val());
break;
}
case AttrPublic: {
// Check whether a superclass has already declared this as a
// protected/public property.
PropMap::Builder::iterator it2 = curPropMap.find(preProp->name());
if (it2 != curPropMap.end()) {
Prop& prop = curPropMap[it2->second];
if ((prop.m_attrs & (AttrPublic|AttrProtected|AttrPrivate))
== AttrProtected) {
// Weaken protected property to public.
prop.m_mangledName = preProp->mangledName();
prop.m_originalMangledName = preProp->mangledName();
prop.m_attrs = Attr(prop.m_attrs ^ (AttrProtected|AttrPublic));
prop.m_typeConstraint = preProp->typeConstraint();
}
const TypedValue& tv = m_preClass->lookupProp(preProp->name())->val();
TypedValueAux& tvaux = m_declPropInit[it2->second];
tvaux.m_data = tv.m_data;
tvaux.m_type = tv.m_type;
copyDeepInitAttr(preProp, &curPropMap[it2->second]);
break;
}
// Append a new public property.
Prop prop;
prop.m_name = preProp->name();
prop.m_mangledName = preProp->mangledName();
prop.m_originalMangledName = preProp->mangledName();
prop.m_attrs = preProp->attrs();
prop.m_typeConstraint = preProp->typeConstraint();
// This is the first class to declare this property
prop.m_class = this;
prop.m_docComment = preProp->docComment();
prop.m_hphpcType = preProp->hphpcType();
curPropMap.add(preProp->name(), prop);
m_declPropInit.push_back(m_preClass->lookupProp(preProp->name())
->val());
break;
}
default: assert(false);
}
} else { // Static property.
// Prohibit non-static-->static redeclaration.
PropMap::Builder::iterator it2 = curPropMap.find(preProp->name());
if (it2 != curPropMap.end()) {
// Find class that declared non-static property.
Class* ancestor;
for (ancestor = m_parent.get();
!ancestor->m_preClass->hasProp(preProp->name());
ancestor = ancestor->m_parent.get()) {
}
raise_error("Cannot redeclare non-static %s::$%s as static %s::$%s",
ancestor->name()->data(),
preProp->name()->data(),
m_preClass->name()->data(),
preProp->name()->data());
}
// Get parent's equivalent property, if one exists.
SPropMap::Builder::iterator it3 = curSPropMap.find(preProp->name());
Slot sPropInd = kInvalidSlot;
// Prohibit strengthening.
if (it3 != curSPropMap.end()) {
const SProp& parentSProp = curSPropMap[it3->second];
if ((preProp->attrs() & (AttrPublic|AttrProtected|AttrPrivate))
> (parentSProp.m_attrs & (AttrPublic|AttrProtected|AttrPrivate))) {
raise_error(
"Access level to %s::$%s() must be %s (as in class %s) or weaker",
m_preClass->name()->data(), preProp->name()->data(),
attrToVisibilityStr(parentSProp.m_attrs),
m_parent->name()->data());
}
sPropInd = it3->second;
}
// Create a new property, or overlay ancestor's property if one exists.
if (sPropInd == kInvalidSlot) {
SProp sProp;
sProp.m_name = preProp->name();
sPropInd = curSPropMap.size();
curSPropMap.add(sProp.m_name, sProp);
}
SProp& sProp = curSPropMap[sPropInd];
// Finish initializing.
sProp.m_attrs = preProp->attrs();
sProp.m_typeConstraint = preProp->typeConstraint();
sProp.m_docComment = preProp->docComment();
sProp.m_class = this;
sProp.m_val = m_preClass->lookupProp(preProp->name())->val();
}
}
importTraitProps(curPropMap, curSPropMap);
m_declProperties.create(curPropMap);
m_staticProperties.create(curSPropMap);
m_declPropNumAccessible = m_declProperties.size() - numInaccessible;
}
bool Class::compatibleTraitPropInit(TypedValue& tv1, TypedValue& tv2) {
if (tv1.m_type != tv2.m_type) return false;
switch (tv1.m_type) {
case KindOfNull: return true;
case KindOfBoolean:
case KindOfInt64:
case KindOfDouble:
case KindOfStaticString:
case KindOfString:
return same(tvAsVariant(&tv1), tvAsVariant(&tv2));
default: return false;
}
}
void Class::importTraitInstanceProp(Class* trait,
Prop& traitProp,
TypedValue& traitPropVal,
PropMap::Builder& curPropMap) {
PropMap::Builder::iterator prevIt = curPropMap.find(traitProp.m_name);
if (prevIt == curPropMap.end()) {
// New prop, go ahead and add it
Prop prop = traitProp;
prop.m_class = this; // set current class as the first declaring prop
// private props' mangled names contain the class name, so regenerate them
if (prop.m_attrs & AttrPrivate) {
prop.m_mangledName = PreClass::manglePropName(m_preClass->name(),
prop.m_name,
prop.m_attrs);
}
curPropMap.add(prop.m_name, prop);
m_declPropInit.push_back(traitPropVal);
} else {
// Redeclared prop, make sure it matches previous declarations
Prop& prevProp = curPropMap[prevIt->second];
TypedValue& prevPropVal = m_declPropInit[prevIt->second];
if (prevProp.m_attrs != traitProp.m_attrs ||
!compatibleTraitPropInit(prevPropVal, traitPropVal)) {
raise_error("trait declaration of property '%s' is incompatible with "
"previous declaration", traitProp.m_name->data());
}
}
}
void Class::importTraitStaticProp(Class* trait,
SProp& traitProp,
PropMap::Builder& curPropMap,
SPropMap::Builder& curSPropMap) {
// Check if prop already declared as non-static
if (curPropMap.find(traitProp.m_name) != curPropMap.end()) {
raise_error("trait declaration of property '%s' is incompatible with "
"previous declaration", traitProp.m_name->data());
}
SPropMap::Builder::iterator prevIt = curSPropMap.find(traitProp.m_name);
if (prevIt == curSPropMap.end()) {
// New prop, go ahead and add it
SProp prop = traitProp;
prop.m_class = this; // set current class as the first declaring prop
curSPropMap.add(prop.m_name, prop);
} else {
// Redeclared prop, make sure it matches previous declaration
SProp& prevProp = curSPropMap[prevIt->second];
TypedValue prevPropVal;
if (prevProp.m_class == this) {
// If this static property was declared by this class, we can
// get the initial value directly from m_val
prevPropVal = prevProp.m_val;
} else {
// If this static property was declared in a parent class, m_val
// will be KindOfUninit, and we'll need to consult the appropriate
// parent class to get the initial value.
prevPropVal = getStaticPropInitVal(prevProp);
}
if (prevProp.m_attrs != traitProp.m_attrs ||
!compatibleTraitPropInit(traitProp.m_val, prevPropVal)) {
raise_error("trait declaration of property '%s' is incompatible with "
"previous declaration", traitProp.m_name->data());
}
prevProp.m_class = this;
prevProp.m_val = prevPropVal;
}
}
void Class::importTraitProps(PropMap::Builder& curPropMap,
SPropMap::Builder& curSPropMap) {
if (attrs() & AttrNoExpandTrait) return;
for (auto& t : m_usedTraits) {
Class* trait = t.get();
// instance properties
for (Slot p = 0; p < trait->m_declProperties.size(); p++) {
Prop& traitProp = trait->m_declProperties[p];
TypedValue& traitPropVal = trait->m_declPropInit[p];
importTraitInstanceProp(trait, traitProp, traitPropVal,
curPropMap);
}
// static properties
for (Slot p = 0; p < trait->m_staticProperties.size(); ++p) {
SProp& traitProp = trait->m_staticProperties[p];
importTraitStaticProp(trait, traitProp, curPropMap,
curSPropMap);
}
}
}
void Class::addTraitPropInitializers(bool staticProps) {
if (attrs() & AttrNoExpandTrait) return;
for (unsigned t = 0; t < m_usedTraits.size(); t++) {
Class* trait = m_usedTraits[t].get();
InitVec& traitInitVec = staticProps ? trait->m_sinitVec : trait->m_pinitVec;
InitVec& thisInitVec = staticProps ? m_sinitVec : m_pinitVec;
// Insert trait's 86[ps]init into the current class, avoiding repetitions.
for (unsigned m = 0; m < traitInitVec.size(); m++) {
// Linear search, but these vectors shouldn't be big.
if (find(thisInitVec.begin(), thisInitVec.end(), traitInitVec[m]) ==
thisInitVec.end()) {
thisInitVec.push_back(traitInitVec[m]);
}
}
}
}
void Class::setInitializers() {
if (m_parent.get() != nullptr) {
// Copy parent's 86pinit() vector, so that the 86pinit() methods can be
// called in reverse order without any search/recursion during
// initialization.
m_pinitVec = m_parent->m_pinitVec;
}
// This class only has a __[ps]init() method if it's needed. Append to the
// vectors of __[ps]init() methods, so that reverse iteration of the vectors
// runs this class's __[ps]init() first, in case multiple classes in the
// hierarchy initialize the same property.
const Func* meth86pinit = findSpecialMethod(this, sd86pinit);
if (meth86pinit != nullptr) {
m_pinitVec.push_back(meth86pinit);
}
addTraitPropInitializers(false);
const Func* sinit = findSpecialMethod(this, sd86sinit);
if (sinit) {
m_sinitVec.push_back(sinit);
}
addTraitPropInitializers(true);
m_needInitialization = (m_pinitVec.size() > 0 ||
m_staticProperties.size() > 0);
m_hasInitMethods = (m_pinitVec.size() > 0 || m_sinitVec.size() > 0);
// The __init__ method defined in the Exception class gets special treatment
static StringData* sd__init__ = StringData::GetStaticString("__init__");
static StringData* sd_exn = StringData::GetStaticString("Exception");
const Func* einit = lookupMethod(sd__init__);
m_callsCustomInstanceInit =
(einit && einit->preClass()->name()->isame(sd_exn));
}
// Checks if interface methods are OK:
// - there's no requirement if this is a trait, interface, or abstract class
// - a non-abstract class must implement all methods from interfaces it
// declares to implement (either directly or indirectly), arity must be
// compatible (at least as many parameters, additional parameters must have
// defaults), and typehints must be compatible
void Class::checkInterfaceMethods() {
for (int i = 0, size = m_interfaces.size(); i < size; i++) {
const Class* iface = m_interfaces[i];
for (size_t m = 0; m < iface->m_methods.size(); m++) {
Func* imeth = iface->m_methods[m];
const StringData* methName = imeth->name();
// Skip special methods
if (Func::isSpecial(methName)) continue;
Func* meth = lookupMethod(methName);
if (attrs() & (AttrTrait | AttrInterface | AttrAbstract)) {
if (meth == nullptr) {
// Skip unimplemented method.
continue;
}
} else {
// Verify that method is not abstract within concrete class.
if (meth == nullptr || (meth->attrs() & AttrAbstract)) {
raise_error("Class %s contains abstract method (%s) and "
"must therefore be declared abstract or implement "
"the remaining methods", name()->data(),
methName->data());
}
}
bool ifaceStaticMethod = imeth->attrs() & AttrStatic;
bool classStaticMethod = meth->attrs() & AttrStatic;
if (classStaticMethod != ifaceStaticMethod) {
raise_error("Cannot make %sstatic method %s::%s() %sstatic "
"in class %s",
ifaceStaticMethod ? "" : "non-",
iface->m_preClass->name()->data(), methName->data(),
classStaticMethod ? "" : "non-",
m_preClass->name()->data());
}
if ((imeth->attrs() & AttrPublic) &&
!(meth->attrs() & AttrPublic)) {
raise_error("Access level to %s::%s() must be public "
"(as in interface %s)", m_preClass->name()->data(),
methName->data(), iface->m_preClass->name()->data());
}
meth->parametersCompat(m_preClass.get(), imeth);
}
}
}
void Class::setInterfaces() {
InterfaceMap::Builder interfacesBuilder;
if (m_parent.get() != nullptr) {
int size = m_parent->m_interfaces.size();
for (int i = 0; i < size; i++) {
Class* interface = m_parent->m_interfaces[i];
interfacesBuilder.add(interface->name(), interface);
}
}
for (PreClass::InterfaceVec::const_iterator it =
m_preClass->interfaces().begin();
it != m_preClass->interfaces().end(); ++it) {
Class* cp = Unit::loadClass(*it);
if (cp == nullptr) {
raise_error("Undefined interface: %s", (*it)->data());
}
if (!(cp->attrs() & AttrInterface)) {
raise_error("%s cannot implement %s - it is not an interface",
m_preClass->name()->data(), cp->name()->data());
}
m_declInterfaces.push_back(ClassPtr(cp));
if (interfacesBuilder.find(cp->name()) == interfacesBuilder.end()) {
interfacesBuilder.add(cp->name(), cp);
}
int size = cp->m_interfaces.size();
for (int i = 0; i < size; i++) {
Class* interface = cp->m_interfaces[i];
interfacesBuilder.find(interface->name());
if (interfacesBuilder.find(interface->name()) ==
interfacesBuilder.end()) {
interfacesBuilder.add(interface->name(), interface);
}
}
}
m_interfaces.create(interfacesBuilder);
checkInterfaceMethods();
}
void Class::setUsedTraits() {
for (PreClass::UsedTraitVec::const_iterator
it = m_preClass->usedTraits().begin();
it != m_preClass->usedTraits().end(); it++) {
Class* classPtr = Unit::loadClass(*it);
if (classPtr == nullptr) {
raise_error("Trait '%s' not found", (*it)->data());
}
if (!(classPtr->attrs() & AttrTrait)) {
raise_error("%s cannot use %s - it is not a trait",
m_preClass->name()->data(),
classPtr->name()->data());
}
m_usedTraits.push_back(ClassPtr(classPtr));
}
}
void Class::setClassVec() {
if (m_classVecLen > 1) {
assert(m_parent.get() != nullptr);
memcpy(m_classVec, m_parent->m_classVec,
(m_classVecLen-1) * sizeof(Class*));
}
m_classVec[m_classVecLen-1] = this;
}
void Class::setInstanceBits() {
setInstanceBitsImpl<false>();
}
void Class::setInstanceBitsAndParents() {
setInstanceBitsImpl<true>();
}
template<bool setParents>
void Class::setInstanceBitsImpl() {
// Bit 0 is reserved to indicate whether or not the rest of the bits
// are initialized yet.
if (m_instanceBits.test(0)) return;
InstanceBits bits;
bits.set(0);
auto setBits = [&](Class* c) {
if (setParents) c->setInstanceBitsAndParents();
bits |= c->m_instanceBits;
};
if (m_parent.get()) setBits(m_parent.get());
for (auto& di : m_declInterfaces) setBits(di.get());
unsigned bit;
if (mapGet(s_instanceBits, m_preClass->name(), &bit)) {
bits.set(bit);
}
m_instanceBits = bits;
}
// Finds the base class defining the given method (NULL if none).
// Note: for methods imported via traits, the base class is the one that
// uses/imports the trait.
Class* Class::findMethodBaseClass(const StringData* methName) {
const Func* f = lookupMethod(methName);
if (f == nullptr) return nullptr;
return f->baseCls();
}
void Class::getMethodNames(const Class* ctx, HphpArray* methods) const {
Func* const* pcMethods = m_preClass->methods();
for (size_t i = 0, sz = m_preClass->numMethods(); i < sz; i++) {
Func* func = pcMethods[i];
if (func->isGenerated()) continue;
if (!(func->attrs() & AttrPublic)) {
if (!ctx) continue;
if (ctx != this) {
if (func->attrs() & AttrPrivate) continue;
func = lookupMethod(func->name());
if (!ctx->classof(func->baseCls()) &&
!func->baseCls()->classof(ctx)) {
continue;
}
}
}
methods->set(const_cast<StringData*>(func->name()), true_varNR, false);
}
if (m_parent.get()) m_parent->getMethodNames(ctx, methods);
for (int i = 0, sz = m_declInterfaces.size(); i < sz; i++) {
m_declInterfaces[i]->getMethodNames(ctx, methods);
}
}
// Returns true iff this class declared the given method.
// For trait methods, the class declaring them is the one that uses/imports
// the trait.
bool Class::declaredMethod(const Func* method) {
if (method->preClass()->attrs() & AttrTrait) {
return findMethodBaseClass(method->name()) == this;
}
return method->preClass() == m_preClass.get();
}
void Class::getClassInfo(ClassInfoVM* ci) {
assert(ci);
// Miscellaneous.
Attr clsAttrs = attrs();
int attr = 0;
if (clsAttrs & AttrInterface) attr |= ClassInfo::IsInterface;
if (clsAttrs & AttrAbstract) attr |= ClassInfo::IsAbstract;
if (clsAttrs & AttrFinal) attr |= ClassInfo::IsFinal;
if (clsAttrs & AttrTrait) attr |= ClassInfo::IsTrait;
if (attr == 0) attr = ClassInfo::IsNothing;
ci->m_attribute = (ClassInfo::Attribute)attr;
ci->m_name = m_preClass->name()->data();
ci->m_file = m_preClass->unit()->filepath()->data();
ci->m_line1 = m_preClass->line1();
ci->m_line2 = m_preClass->line2();
ci->m_docComment = (m_preClass->docComment() != nullptr)
? m_preClass->docComment()->data() : "";
// Parent class.
if (m_parent.get()) {
ci->m_parentClass = m_parent->name()->data();
} else {
ci->m_parentClass = "";
}
// Interfaces.
for (unsigned i = 0; i < m_declInterfaces.size(); i++) {
ci->m_interfacesVec.push_back(
m_declInterfaces[i]->name()->data());
ci->m_interfaces.insert(
m_declInterfaces[i]->name()->data());
}
// Used traits.
for (unsigned t = 0; t < m_usedTraits.size(); t++) {
const char* traitName = m_usedTraits[t]->name()->data();
ci->m_traitsVec.push_back(traitName);
ci->m_traits.insert(traitName);
}
// Trait aliases.
for (unsigned a = 0; a < m_traitAliases.size(); a++) {
ci->m_traitAliasesVec.push_back(std::pair<String, String>
(m_traitAliases[a].first->data(),
m_traitAliases[a].second->data()));
}
#define SET_FUNCINFO_BODY \
ClassInfo::MethodInfo *m = new ClassInfo::MethodInfo; \
func->getFuncInfo(m); \
ci->m_methods[func->name()->data()] = m; \
ci->m_methodsVec.push_back(m);
// Methods: in source order (from our PreClass), then traits.
for (size_t i = 0; i < m_preClass->numMethods(); ++i) {
Func* func = lookupMethod(m_preClass->methods()[i]->name());
// Filter out special methods
if (!func) {
DEBUG_ONLY const StringData* name = m_preClass->methods()[i]->name();
assert(!strcmp(name->data(), "86ctor"));
continue;
}
if (func->isGenerated()) continue;
assert(func);
assert(declaredMethod(func));
SET_FUNCINFO_BODY;
}
for (Slot i = m_traitsBeginIdx; i < m_traitsEndIdx; ++i) {
Func* func = m_methods[i];
assert(func);
if (!func->isGenerated()) {
SET_FUNCINFO_BODY;
}
}
#undef SET_FUNCINFO_BODY
// Properties.
for (Slot i = 0; i < m_declProperties.size(); ++i) {
if (m_declProperties[i].m_class != this) continue;
ClassInfo::PropertyInfo *pi = new ClassInfo::PropertyInfo;
pi->owner = ci;
pi->name = m_declProperties[i].m_name->data();
Attr propAttrs = m_declProperties[i].m_attrs;
attr = 0;
if (propAttrs & AttrProtected) attr |= ClassInfo::IsProtected;
if (propAttrs & AttrPrivate) attr |= ClassInfo::IsPrivate;
if (attr == 0) attr |= ClassInfo::IsPublic;
if (propAttrs & AttrStatic) attr |= ClassInfo::IsStatic;
pi->attribute = (ClassInfo::Attribute)attr;
pi->docComment = (m_declProperties[i].m_docComment != nullptr)
? m_declProperties[i].m_docComment->data() : "";
ci->m_properties[pi->name] = pi;
ci->m_propertiesVec.push_back(pi);
}
for (Slot i = 0; i < m_staticProperties.size(); ++i) {
if (m_staticProperties[i].m_class != this) continue;
ClassInfo::PropertyInfo *pi = new ClassInfo::PropertyInfo;
pi->owner = ci;
pi->name = m_staticProperties[i].m_name->data();
Attr propAttrs = m_staticProperties[i].m_attrs;
attr = 0;
if (propAttrs & AttrProtected) attr |= ClassInfo::IsProtected;
if (propAttrs & AttrPrivate) attr |= ClassInfo::IsPrivate;
if (attr == 0) attr |= ClassInfo::IsPublic;
if (propAttrs & AttrStatic) attr |= ClassInfo::IsStatic;
pi->attribute = (ClassInfo::Attribute)attr;
pi->docComment = (m_staticProperties[i].m_docComment != nullptr)
? m_staticProperties[i].m_docComment->data() : "";
ci->m_properties[pi->name] = pi;
ci->m_propertiesVec.push_back(pi);
}
// Constants.
for (Slot i = 0; i < m_constants.size(); ++i) {
// Only include constants declared on this class
if (m_constants[i].m_class != this) continue;
ClassInfo::ConstantInfo *ki = new ClassInfo::ConstantInfo;
ki->name = m_constants[i].m_name->data();
ki->valueLen = m_constants[i].m_phpCode->size();
ki->valueText = m_constants[i].m_phpCode->data();
ki->setValue(tvAsCVarRef(clsCnsGet(m_constants[i].m_name)));
ci->m_constants[ki->name] = ki;
ci->m_constantsVec.push_back(ki);
}
}
size_t Class::declPropOffset(Slot index) const {
assert(index >= 0);
return sizeof(ObjectData) + m_builtinPropSize
+ index * sizeof(TypedValue);
}
Class::PropInitVec::~PropInitVec() {
if (!m_smart) free(m_data);
}
Class::PropInitVec::PropInitVec() : m_data(nullptr), m_size(0), m_smart(false) {}
Class::PropInitVec*
Class::PropInitVec::allocInRequestArena(const PropInitVec& src) {
ThreadInfo* info UNUSED = ThreadInfo::s_threadInfo.getNoCheck();
PropInitVec* p = new (request_arena()) PropInitVec;
p->m_size = src.size();
p->m_data = new (request_arena()) TypedValueAux[src.size()];
memcpy(p->m_data, src.m_data, src.size() * sizeof(*p->m_data));
p->m_smart = true;
return p;
}
const Class::PropInitVec&
Class::PropInitVec::operator=(const PropInitVec& piv) {
assert(!m_smart);
if (this != &piv) {
unsigned sz = m_size = piv.size();
if (sz) sz = Util::roundUpToPowerOfTwo(sz);
free(m_data);
m_data = (TypedValueAux*)malloc(sz * sizeof(*m_data));
assert(m_data);
memcpy(m_data, piv.m_data, piv.size() * sizeof(*m_data));
}
return *this;
}
void Class::PropInitVec::push_back(const TypedValue& v) {
assert(!m_smart);
/*
* the allocated size is always the next power of two (or zero)
* so we just need to reallocate when we hit a power of two
*/
if (!m_size || Util::isPowerOfTwo(m_size)) {
unsigned size = m_size ? m_size * 2 : 1;
m_data = (TypedValueAux*)realloc(m_data, size * sizeof(*m_data));
assert(m_data);
}
tvDup(v, m_data[m_size++]);
}
using Transl::TargetCache::handleToRef;
const Class::PropInitVec* Class::getPropData() const {
if (m_propDataCache == (unsigned)-1) return nullptr;
return handleToRef<PropInitVec*>(m_propDataCache);
}
void Class::initPropHandle() const {
if (UNLIKELY(m_propDataCache == (unsigned)-1)) {
const_cast<unsigned&>(m_propDataCache) =
Transl::TargetCache::allocClassInitProp(name());
}
}
void Class::initProps() const {
setPropData(initPropsImpl());
}
void Class::setPropData(PropInitVec* propData) const {
assert(getPropData() == nullptr);
initPropHandle();
handleToRef<PropInitVec*>(m_propDataCache) = propData;
}
TypedValue* Class::getSPropData() const {
if (m_propSDataCache == (unsigned)-1) return nullptr;
return handleToRef<TypedValue*>(m_propSDataCache);
}
void Class::initSPropHandle() const {
if (UNLIKELY(m_propSDataCache == (unsigned)-1)) {
const_cast<unsigned&>(m_propSDataCache) =
Transl::TargetCache::allocClassInitSProp(name());
}
}
TypedValue* Class::initSProps() const {
TypedValue* sprops = initSPropsImpl();
setSPropData(sprops);
return sprops;
}
void Class::setSPropData(TypedValue* sPropData) const {
assert(getSPropData() == nullptr);
initSPropHandle();
handleToRef<TypedValue*>(m_propSDataCache) = sPropData;
}
void Class::getChildren(std::vector<TypedValue *> &out) {
for (Slot i = 0; i < m_staticProperties.size(); ++i) {
if (m_staticProperties[i].m_class != this) continue;
out.push_back(&m_staticProperties[i].m_val);
}
}
// True if a CPP extension class has opted into serialization.
bool Class::isCppSerializable() const {
assert(builtinPropSize() > 0); // Only call this on CPP classes
return clsInfo() &&
(clsInfo()->getAttribute() & ClassInfo::IsCppSerializable);
}
} // HPHP::VM