9edc07112b
When object support was first added to HHVM, a class named "Instance" was introduced (deriving from ObjectData) to represent instances of user defined classes. Since then, things have evolved and HPHPc and HPHPi have been retired, and now there really is no needed to have ObjectData and Instance be separate classes anymore. As a first step towards merging ObjectData and Instance together, this diff puts their definitions in the same .h file and puts their implementations in the same .cpp file. A few small changes were necessary to fix issues with cyclical includes: (1) Repo/emitter related parts of class.cpp and class.h were moved to class-emit.cpp and class-emit.h; (2) the contents of "vm/core_types.h" was moved to "base/types.h"; and (3) a few functions that didn't appear to be hot were moved from .h files and the corresponding .cpp files.
2343 linhas
83 KiB
C++
2343 linhas
83 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/array/hphp_array.h"
|
|
#include "hphp/util/util.h"
|
|
#include "hphp/util/debug.h"
|
|
#include "hphp/runtime/vm/jit/targetcache.h"
|
|
#include "hphp/runtime/vm/jit/translator.h"
|
|
#include "hphp/runtime/vm/treadmill.h"
|
|
#include "hphp/runtime/vm/name_value_table.h"
|
|
#include "hphp/runtime/vm/name_value_table_wrapper.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 <boost/optional.hpp>
|
|
#include <boost/utility/typed_in_place_factory.hpp>
|
|
|
|
#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.
|
|
|
|
ClassPtr Class::newClass(PreClass* preClass, Class* parent) {
|
|
unsigned classVecLen = (parent != nullptr) ? parent->m_classVecLen+1 : 1;
|
|
void* mem = Util::low_malloc(sizeForNClasses(classVecLen));
|
|
try {
|
|
return ClassPtr(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(ClassPtr(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();
|
|
}
|
|
|
|
void Class::atomicRelease() {
|
|
if (m_cachedOffset != 0u) {
|
|
/*
|
|
m_cachedOffset is initialied to 0, and is only set
|
|
when the Class is put on the list, so we only have to
|
|
remove this node if its NOT 0.
|
|
Since we're about to remove it, reset to 0 so we know
|
|
its safe to kill the node during the delayed Treadmill
|
|
callback.
|
|
*/
|
|
m_cachedOffset = 0u;
|
|
PreClass* pcls = m_preClass.get();
|
|
{
|
|
Lock l(Unit::s_classesMutex);
|
|
pcls->namedEntity()->removeClass(this);
|
|
}
|
|
Treadmill::WorkItem::enqueue(new Treadmill::FreeClassTrigger(this));
|
|
return;
|
|
}
|
|
|
|
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 (size_t i = 0, nInterfaces = m_declInterfaces.size();
|
|
i < nInterfaces; ++i) {
|
|
Class* declInterface = m_declInterfaces[i].get();
|
|
if (!Transl::TargetCache::isPersistentHandle(
|
|
declInterface->m_cachedOffset)) {
|
|
return false;
|
|
}
|
|
}
|
|
for (size_t i = 0; i < m_usedTraits.size(); i++) {
|
|
Class* usedTrait = m_usedTraits[i].get();
|
|
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) return Avail::False;
|
|
}
|
|
for (size_t i = 0, nInterfaces = m_declInterfaces.size();
|
|
i < nInterfaces; ++i) {
|
|
Class* declInterface = m_declInterfaces[i].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;
|
|
}
|
|
return Avail::False;
|
|
}
|
|
}
|
|
for (size_t i = 0; i < m_usedTraits.size(); i++) {
|
|
Class* usedTrait = m_usedTraits[i].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;
|
|
}
|
|
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()];
|
|
|
|
boost::optional<NameValueTable> nvt;
|
|
const bool hasNonscalarInit = !m_sinitVec.empty();
|
|
if (hasNonscalarInit) {
|
|
nvt = boost::in_place<NameValueTable>(m_staticProperties.size());
|
|
}
|
|
|
|
// Iteratively initialize properties. Non-scalar initializers are
|
|
// initialized to KindOfUninit here, and the 86sinit()-based initialization
|
|
// finishes the job later.
|
|
for (Slot slot = 0; slot < m_staticProperties.size(); ++slot) {
|
|
const SProp& sProp = m_staticProperties[slot];
|
|
|
|
TypedValue* storage = 0;
|
|
if (sProp.m_class == this) {
|
|
// Embed static property value directly in array.
|
|
assert(tvIsStatic(&sProp.m_val));
|
|
spropTable[slot] = sProp.m_val;
|
|
storage = &spropTable[slot];
|
|
} else {
|
|
// Alias parent class's static property.
|
|
bool visible, accessible;
|
|
storage = sProp.m_class->getSProp(nullptr, sProp.m_name, visible,
|
|
accessible);
|
|
tvBindIndirect(&spropTable[slot], storage);
|
|
}
|
|
|
|
if (hasNonscalarInit) {
|
|
nvt->migrateSet(sProp.m_name, storage);
|
|
}
|
|
}
|
|
|
|
// Invoke 86sinit's if necessary, to handle non-scalar initializers.
|
|
if (hasNonscalarInit) {
|
|
// See note in initPropsImpl for why its ok to allocate
|
|
// this on the stack.
|
|
NameValueTableWrapper nvtWrapper(&*nvt);
|
|
nvtWrapper.incRefCount();
|
|
|
|
ArrayData* args = ArrayData::Make(1);
|
|
args->incRefCount();
|
|
try {
|
|
{
|
|
Variant arg0(&nvtWrapper);
|
|
args = args->appendRef(arg0, false);
|
|
|
|
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(!IS_REFCOUNTED_TYPE(retval.m_type));
|
|
}
|
|
}
|
|
// Release the args array. nvtWrapper is on the stack, so it
|
|
// better have a single reference.
|
|
assert(args->getCount() == 1);
|
|
args->release();
|
|
assert(nvtWrapper.getCount() == 1);
|
|
} catch (...) {
|
|
assert(args->getCount() == 1);
|
|
args->release();
|
|
assert(nvtWrapper.getCount() == 1);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
TypedValue* Class::cnsNameToTV(const StringData* clsCnsName,
|
|
Slot& clsCnsInd) const {
|
|
clsCnsInd = m_constants.findIndex(clsCnsName);
|
|
if (clsCnsInd == kInvalidSlot) {
|
|
return nullptr;
|
|
}
|
|
return const_cast<TypedValue*>(&m_constants[clsCnsInd].m_val);
|
|
}
|
|
|
|
TypedValue* Class::clsCnsGet(const StringData* clsCnsName) const {
|
|
Slot clsCnsInd;
|
|
TypedValue* 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);
|
|
}
|
|
return clsCns;
|
|
}
|
|
|
|
DataType Class::clsCnsType(const StringData* cnsName) const {
|
|
Slot slot;
|
|
TypedValue* 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());
|
|
}
|
|
}
|
|
|
|
ClassPtr Class::findSingleTraitWithMethod(const StringData* methName) {
|
|
// Note: m_methods includes methods from parents / traits recursively
|
|
ClassPtr traitCls = ClassPtr();
|
|
for (size_t t = 0; t < m_usedTraits.size(); t++) {
|
|
if (m_usedTraits[t]->m_methods.contains(methName)) {
|
|
if (traitCls.get() != nullptr) { // more than one trait contains method
|
|
return ClassPtr();
|
|
}
|
|
traitCls = m_usedTraits[t];
|
|
}
|
|
}
|
|
return traitCls;
|
|
}
|
|
|
|
void Class::setImportTraitMethodModifiers(TraitMethodList& methList,
|
|
ClassPtr traitCls,
|
|
Attr modifiers) {
|
|
for (TraitMethodList::iterator iter = methList.begin();
|
|
iter != methList.end(); iter++) {
|
|
if (iter->m_trait.get() == traitCls.get()) {
|
|
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();
|
|
|
|
ClassPtr traitCls;
|
|
if (traitName->empty()) {
|
|
traitCls = findSingleTraitWithMethod(origMethName);
|
|
} else {
|
|
traitCls = Unit::loadClass(traitName);
|
|
}
|
|
|
|
if (!traitCls.get() || (!(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) {
|
|
ClassPtr trait = traitMethod.m_trait;
|
|
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 (size_t t = 0; t < m_usedTraits.size(); t++) {
|
|
ClassPtr trait = m_usedTraits[t];
|
|
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 (std::vector<ClassPtr>::const_iterator 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(ClassPtr 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(ClassPtr 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 (size_t t = 0; t < m_usedTraits.size(); t++) {
|
|
ClassPtr trait = m_usedTraits[t];
|
|
|
|
// 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++) {
|
|
ClassPtr trait = m_usedTraits[t];
|
|
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) {
|
|
auto cp = ClassPtr(Unit::loadClass(*it));
|
|
if (cp.get() == 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(cp);
|
|
if (interfacesBuilder.find(cp->name()) == interfacesBuilder.end()) {
|
|
interfacesBuilder.add(cp->name(), cp.get());
|
|
}
|
|
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++) {
|
|
auto classPtr = ClassPtr(Unit::loadClass(*it));
|
|
if (classPtr.get() == 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);
|
|
}
|
|
}
|
|
|
|
void Class::setClassVec() {
|
|
if (m_classVecLen > 1) {
|
|
assert(m_parent.get() != nullptr);
|
|
memcpy(m_classVec, m_parent.get()->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 = [&](ClassPtr& c) {
|
|
if (setParents) c->setInstanceBitsAndParents();
|
|
bits |= c->m_instanceBits;
|
|
};
|
|
if (m_parent.get()) setBits(m_parent);
|
|
std::for_each(m_declInterfaces.begin(), m_declInterfaces.end(), setBits);
|
|
|
|
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.get()->getMethodNames(ctx, methods);
|
|
for (int i = 0, sz = m_declInterfaces.size(); i < sz; i++) {
|
|
m_declInterfaces[i].get()->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);
|
|
}
|
|
}
|
|
|
|
} // HPHP::VM
|