363d1bb20f
This change is mostly for FB internal organizational reasons. Building is not effected beyond the fact that the target now lands in hphp/hhvm/hhvm rather than src/hhvm/hhvm.
456 linhas
12 KiB
C++
456 linhas
12 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010- 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 <runtime/base/array/array_iterator.h>
|
|
#include <runtime/base/array/array_data.h>
|
|
#include <runtime/base/array/hphp_array.h>
|
|
#include <runtime/base/complex_types.h>
|
|
#include <runtime/base/object_data.h>
|
|
#include <runtime/ext/ext_collection.h>
|
|
|
|
namespace HPHP {
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Static strings.
|
|
|
|
static StaticString s_rewind("rewind");
|
|
static StaticString s_valid("valid");
|
|
static StaticString s_next("next");
|
|
static StaticString s_key("key");
|
|
static StaticString s_current("current");
|
|
static StaticString s_Iterator("Iterator");
|
|
static StaticString s_Continuation("Continuation");
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// ArrayIter
|
|
|
|
ArrayIter::ArrayIter() : m_pos(ArrayData::invalid_index) {
|
|
m_data = NULL;
|
|
}
|
|
|
|
HOT_FUNC
|
|
ArrayIter::ArrayIter(const ArrayData *data) {
|
|
setArrayData(data);
|
|
if (data) {
|
|
data->incRefCount();
|
|
m_pos = data->iter_begin();
|
|
} else {
|
|
m_pos = ArrayData::invalid_index;
|
|
}
|
|
}
|
|
|
|
HOT_FUNC
|
|
ArrayIter::ArrayIter(CArrRef array) : m_pos(0) {
|
|
const ArrayData* ad = array.get();
|
|
setArrayData(ad);
|
|
if (ad) {
|
|
ad->incRefCount();
|
|
m_pos = ad->iter_begin();
|
|
} else {
|
|
m_pos = ArrayData::invalid_index;
|
|
}
|
|
}
|
|
|
|
void ArrayIter::reset() {
|
|
if (hasArrayData()) {
|
|
const ArrayData* ad = getArrayData();
|
|
m_data = NULL;
|
|
if (ad) decRefArr(const_cast<ArrayData*>(ad));
|
|
return;
|
|
}
|
|
ObjectData* obj = getRawObject();
|
|
m_data = NULL;
|
|
assert(obj);
|
|
decRefObj(obj);
|
|
}
|
|
|
|
void ArrayIter::begin(CVarRef map, CStrRef context) {
|
|
try {
|
|
new (this) ArrayIter(map.begin(context));
|
|
} catch (...) {
|
|
m_data = NULL;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void ArrayIter::begin(CArrRef map, CStrRef context) {
|
|
try {
|
|
new (this) ArrayIter(map.get());
|
|
} catch (...) {
|
|
m_data = NULL;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
template <bool incRef>
|
|
void ArrayIter::objInit(ObjectData *obj) {
|
|
assert(obj);
|
|
setObject(obj);
|
|
if (incRef) {
|
|
obj->incRefCount();
|
|
}
|
|
if (!obj->isCollection()) {
|
|
assert(obj->o_instanceof(s_Iterator));
|
|
obj->o_invoke(s_rewind, Array());
|
|
} else {
|
|
if (hasVector()) {
|
|
c_Vector* vec = getVector();
|
|
m_versionNumber = vec->getVersionNumber();
|
|
m_pos = 0;
|
|
} else if (hasMap()) {
|
|
c_Map* mp = getMap();
|
|
m_versionNumber = mp->getVersionNumber();
|
|
m_pos = mp->iter_begin();
|
|
} else if (hasStableMap()) {
|
|
c_StableMap* smp = getStableMap();
|
|
m_versionNumber = smp->getVersionNumber();
|
|
m_pos = smp->iter_begin();
|
|
} else {
|
|
assert(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
ArrayIter::ArrayIter(ObjectData *obj)
|
|
: m_pos(ArrayData::invalid_index) {
|
|
objInit<true>(obj);
|
|
}
|
|
|
|
ArrayIter::ArrayIter(Object &obj, TransferOwner)
|
|
: m_pos(ArrayData::invalid_index) {
|
|
objInit<false>(obj.get());
|
|
(void) obj.detach();
|
|
}
|
|
|
|
// Special constructor used by the VM. This constructor does not increment the
|
|
// refcount of the specified object.
|
|
ArrayIter::ArrayIter(ObjectData *obj, NoInc)
|
|
: m_pos(ArrayData::invalid_index) {
|
|
objInit<false>(obj);
|
|
}
|
|
|
|
HOT_FUNC
|
|
ArrayIter::~ArrayIter() {
|
|
if (hasArrayData()) {
|
|
const ArrayData* ad = getArrayData();
|
|
if (ad) decRefArr(const_cast<ArrayData*>(ad));
|
|
return;
|
|
}
|
|
ObjectData* obj = getRawObject();
|
|
assert(obj);
|
|
decRefObj(obj);
|
|
}
|
|
|
|
bool ArrayIter::endHelper() {
|
|
if (hasVector()) {
|
|
c_Vector* vec = getVector();
|
|
return m_pos >= vec->t_count();
|
|
}
|
|
if (hasMap()) {
|
|
return m_pos == 0;
|
|
}
|
|
if (hasStableMap()) {
|
|
return m_pos == 0;
|
|
}
|
|
assert(hasObject());
|
|
ObjectData* obj = getObject();
|
|
return !obj->o_invoke(s_valid, Array());
|
|
}
|
|
|
|
void ArrayIter::nextHelper() {
|
|
if (hasVector()) {
|
|
m_pos++;
|
|
return;
|
|
}
|
|
if (hasMap()) {
|
|
assert(m_pos != 0);
|
|
c_Map* mp = getMap();
|
|
if (UNLIKELY(m_versionNumber != mp->getVersionNumber())) {
|
|
throw_collection_modified();
|
|
}
|
|
m_pos = mp->iter_next(m_pos);
|
|
return;
|
|
}
|
|
if (hasStableMap()) {
|
|
assert(m_pos != 0);
|
|
c_StableMap* smp = getStableMap();
|
|
if (UNLIKELY(m_versionNumber != smp->getVersionNumber())) {
|
|
throw_collection_modified();
|
|
}
|
|
m_pos = smp->iter_next(m_pos);
|
|
return;
|
|
}
|
|
assert(hasObject());
|
|
ObjectData* obj = getObject();
|
|
obj->o_invoke(s_next, Array());
|
|
}
|
|
|
|
Variant ArrayIter::firstHelper() {
|
|
if (hasVector()) {
|
|
return m_pos;
|
|
}
|
|
if (hasMap()) {
|
|
assert(m_pos != 0);
|
|
c_Map* mp = getMap();
|
|
if (UNLIKELY(m_versionNumber != mp->getVersionNumber())) {
|
|
throw_collection_modified();
|
|
}
|
|
return mp->iter_key(m_pos);
|
|
}
|
|
if (hasStableMap()) {
|
|
assert(m_pos != 0);
|
|
c_StableMap* smp = getStableMap();
|
|
if (UNLIKELY(m_versionNumber != smp->getVersionNumber())) {
|
|
throw_collection_modified();
|
|
}
|
|
return smp->iter_key(m_pos);
|
|
}
|
|
assert(hasObject());
|
|
ObjectData* obj = getObject();
|
|
return obj->o_invoke(s_key, Array());
|
|
}
|
|
|
|
HOT_FUNC
|
|
Variant ArrayIter::second() {
|
|
if (hasVector()) {
|
|
c_Vector* vec = getVector();
|
|
if (UNLIKELY(m_versionNumber != vec->getVersionNumber())) {
|
|
throw_collection_modified();
|
|
}
|
|
return tvAsCVarRef(vec->at(m_pos));
|
|
}
|
|
if (hasMap()) {
|
|
c_Map* mp = getMap();
|
|
if (UNLIKELY(m_versionNumber != mp->getVersionNumber())) {
|
|
throw_collection_modified();
|
|
}
|
|
return mp->iter_value(m_pos);
|
|
}
|
|
if (hasStableMap()) {
|
|
c_StableMap* smp = getStableMap();
|
|
if (UNLIKELY(m_versionNumber != smp->getVersionNumber())) {
|
|
throw_collection_modified();
|
|
}
|
|
return smp->iter_value(m_pos);
|
|
}
|
|
if (hasObject()) {
|
|
ObjectData* obj = getObject();
|
|
return obj->o_invoke(s_current, Array());
|
|
}
|
|
assert(hasArrayData());
|
|
assert(m_pos != ArrayData::invalid_index);
|
|
const ArrayData* ad = getArrayData();
|
|
assert(ad);
|
|
return ad->getValue(m_pos);
|
|
}
|
|
|
|
void ArrayIter::secondHelper(Variant & v) {
|
|
if (hasVector()) {
|
|
c_Vector* vec = getVector();
|
|
if (UNLIKELY(m_versionNumber != vec->getVersionNumber())) {
|
|
throw_collection_modified();
|
|
}
|
|
v = tvAsCVarRef(vec->at(m_pos));
|
|
return;
|
|
}
|
|
if (hasMap()) {
|
|
c_Map* mp = getMap();
|
|
if (UNLIKELY(m_versionNumber != mp->getVersionNumber())) {
|
|
throw_collection_modified();
|
|
}
|
|
v = mp->iter_value(m_pos);
|
|
return;
|
|
}
|
|
if (hasStableMap()) {
|
|
c_StableMap* smp = getStableMap();
|
|
if (UNLIKELY(m_versionNumber != smp->getVersionNumber())) {
|
|
throw_collection_modified();
|
|
}
|
|
v = smp->iter_value(m_pos);
|
|
return;
|
|
}
|
|
assert(hasObject());
|
|
ObjectData* obj = getObject();
|
|
v = obj->o_invoke(s_current, Array());
|
|
}
|
|
|
|
HOT_FUNC
|
|
CVarRef ArrayIter::secondRef() {
|
|
if (!hasArrayData()) {
|
|
throw FatalErrorException("taking reference on iterator objects");
|
|
}
|
|
assert(hasArrayData());
|
|
assert(m_pos != ArrayData::invalid_index);
|
|
const ArrayData* ad = getArrayData();
|
|
assert(ad);
|
|
return ad->getValueRef(m_pos);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// MutableArrayIter
|
|
|
|
MutableArrayIter::MutableArrayIter(const Variant *var, Variant *key,
|
|
Variant &val)
|
|
: m_var(var), m_data(NULL), m_key(key), m_valp(&val), m_fp() {
|
|
assert(m_var);
|
|
escalateCheck();
|
|
ArrayData* data = cowCheck();
|
|
if (data) {
|
|
data->reset();
|
|
data->newFullPos(m_fp);
|
|
assert(m_fp.container == data);
|
|
}
|
|
}
|
|
|
|
MutableArrayIter::MutableArrayIter(ArrayData *data, Variant *key,
|
|
Variant &val)
|
|
: m_var(NULL), m_data(data), m_key(key), m_valp(&val), m_fp() {
|
|
if (data) {
|
|
escalateCheck();
|
|
data = cowCheck();
|
|
data->reset();
|
|
data->newFullPos(m_fp);
|
|
assert(m_fp.container == data);
|
|
}
|
|
}
|
|
|
|
MutableArrayIter::~MutableArrayIter() {
|
|
// free the iterator
|
|
if (m_fp.container != NULL) {
|
|
m_fp.container->freeFullPos(m_fp);
|
|
assert(m_fp.container == NULL);
|
|
}
|
|
// unprotect the data
|
|
if (m_data) decRefArr(m_data);
|
|
}
|
|
|
|
void MutableArrayIter::reset() {
|
|
if (m_fp.container != NULL) {
|
|
m_fp.container->freeFullPos(m_fp);
|
|
assert(m_fp.container == NULL);
|
|
}
|
|
// unprotect the data
|
|
if (m_data) {
|
|
decRefArr(m_data);
|
|
m_data = NULL;
|
|
}
|
|
}
|
|
|
|
void MutableArrayIter::begin(Variant& map, Variant* key, Variant& val,
|
|
CStrRef context) {
|
|
try {
|
|
new (this) MutableArrayIter(map.begin(key, val, context));
|
|
} catch (...) {
|
|
m_fp.container = NULL;
|
|
m_data = NULL;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
bool MutableArrayIter::advance() {
|
|
ArrayData *data = m_var ? getData() : m_data;
|
|
if (!data) return false;
|
|
// If the foreach loop's array changed since the previous iteration,
|
|
// we recover by creating a new strong iterator for the new array,
|
|
// starting with at the position indicated by the new array's internal
|
|
// pointer.
|
|
if (m_fp.container != data) {
|
|
// Free the current strong iterator if its valid
|
|
if (m_fp.container != NULL) {
|
|
m_fp.container->freeFullPos(m_fp);
|
|
}
|
|
assert(m_fp.container == NULL);
|
|
// If needed, escalate the array to an array type that can support
|
|
// foreach by reference
|
|
escalateCheck();
|
|
// Trigger COW if needed, copying over strong iterators
|
|
data = cowCheck();
|
|
// Create a new strong iterator for the new array
|
|
data->newFullPos(m_fp);
|
|
} else {
|
|
// Trigger COW if needed, copying over strong iterators
|
|
data = cowCheck();
|
|
}
|
|
assert(m_fp.container == data);
|
|
if (!data->setFullPos(m_fp)) return false;
|
|
CVarRef curr = data->currentRef();
|
|
m_valp->assignRef(curr);
|
|
if (m_key) m_key->assignVal(data->key());
|
|
data->next();
|
|
data->getFullPos(m_fp);
|
|
return true;
|
|
}
|
|
|
|
void MutableArrayIter::escalateCheck() {
|
|
ArrayData* data;
|
|
if (m_var) {
|
|
data = getData();
|
|
if (!data) return;
|
|
ArrayData* esc = data->escalate(true);
|
|
if (data != esc) {
|
|
*const_cast<Variant*>(m_var) = esc;
|
|
}
|
|
} else {
|
|
assert(m_data);
|
|
data = m_data;
|
|
ArrayData* esc = data->escalate(true);
|
|
if (data != esc) {
|
|
esc->incRefCount();
|
|
decRefArr(data);
|
|
m_data = esc;
|
|
}
|
|
}
|
|
}
|
|
|
|
ArrayData* MutableArrayIter::cowCheck() {
|
|
ArrayData* data;
|
|
if (m_var) {
|
|
data = getData();
|
|
if (!data) return NULL;
|
|
if (data->getCount() > 1 && !data->noCopyOnWrite()) {
|
|
*const_cast<Variant*>(m_var) = (data = data->copyWithStrongIterators());
|
|
}
|
|
} else {
|
|
assert(m_data);
|
|
data = m_data;
|
|
if (data->getCount() > 1 && !data->noCopyOnWrite()) {
|
|
ArrayData* copied = data->copyWithStrongIterators();
|
|
copied->incRefCount();
|
|
decRefArr(data);
|
|
m_data = data = copied;
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
ArrayData* MutableArrayIter::getData() {
|
|
assert(m_var);
|
|
if (m_var->is(KindOfArray)) {
|
|
return m_var->getArrayData();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
MIterCtx::~MIterCtx() {
|
|
m_mArray->~MutableArrayIter();
|
|
smart_free(m_mArray);
|
|
tvRefcountedDecRef(&m_key);
|
|
tvRefcountedDecRef(&m_val);
|
|
if (m_ref) decRefRef(const_cast<RefData*>(m_ref));
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|