5c486012c9
Adds an extension command to the debugger which provides heap tracing functionality, and adds the framework needed to get heap traces. The heaptrace command can dump the current heap to the debugger session or it can save a GraphViz specification to a file. I'm hoping to add a backend which can put the graph in GML format, so it can be explored interactively. I also hope at some point to see this integrated into FBIDE if we can do that.
600 linhas
17 KiB
C++
600 linhas
17 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/array/array_data.h"
|
|
|
|
#include "tbb/concurrent_hash_map.h"
|
|
|
|
#include "hphp/util/exception.h"
|
|
#include "hphp/runtime/base/array/array_init.h"
|
|
#include "hphp/runtime/base/array/array_iterator.h"
|
|
#include "hphp/runtime/base/type_conversions.h"
|
|
#include "hphp/runtime/base/builtin_functions.h"
|
|
#include "hphp/runtime/base/complex_types.h"
|
|
#include "hphp/runtime/base/variable_serializer.h"
|
|
#include "hphp/runtime/base/runtime_option.h"
|
|
#include "hphp/runtime/base/macros.h"
|
|
#include "hphp/runtime/base/shared/shared_map.h"
|
|
#include "hphp/runtime/base/array/policy_array.h"
|
|
#include "hphp/runtime/base/comparisons.h"
|
|
|
|
namespace HPHP {
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
static_assert(
|
|
sizeof(ArrayData) == 32,
|
|
"Performance is sensitive to sizeof(ArrayData)."
|
|
" Make sure you changed it with good reason and then update this assert.");
|
|
|
|
typedef tbb::concurrent_hash_map<const StringData *, ArrayData *,
|
|
StringDataHashCompare> ArrayDataMap;
|
|
static ArrayDataMap s_arrayDataMap;
|
|
|
|
ArrayData *ArrayData::GetScalarArray(ArrayData *arr,
|
|
const StringData *key /* = nullptr */) {
|
|
if (!key) {
|
|
key = StringData::GetStaticString(f_serialize(arr).get());
|
|
} else {
|
|
assert(key->isStatic());
|
|
assert(key->same(f_serialize(arr).get()));
|
|
}
|
|
ArrayDataMap::accessor acc;
|
|
if (s_arrayDataMap.insert(acc, key)) {
|
|
ArrayData *ad = arr->nonSmartCopy();
|
|
ad->setStatic();
|
|
ad->onSetEvalScalar();
|
|
acc->second = ad;
|
|
}
|
|
return acc->second;
|
|
}
|
|
|
|
// In general, arrays can contain int-valued-strings, even though
|
|
// plain array access converts them to integers. non-int-string
|
|
// assersions should go upstream of the ArrayData api.
|
|
|
|
bool ArrayData::IsValidKey(CStrRef k) {
|
|
return IsValidKey(k.get());
|
|
}
|
|
|
|
bool ArrayData::IsValidKey(CVarRef k) {
|
|
return k.isInteger() ||
|
|
(k.isString() && IsValidKey(k.getStringData()));
|
|
}
|
|
|
|
// constructors/destructors
|
|
|
|
HOT_FUNC
|
|
ArrayData *ArrayData::Create() {
|
|
return ArrayInit((ssize_t)0).create();
|
|
}
|
|
|
|
HOT_FUNC
|
|
ArrayData *ArrayData::Create(CVarRef value) {
|
|
ArrayInit init(1);
|
|
init.set(value);
|
|
return init.create();
|
|
}
|
|
|
|
ArrayData *ArrayData::Create(CVarRef name, CVarRef value) {
|
|
ArrayInit init(1);
|
|
// There is no toKey() call on name.
|
|
init.set(name, value, true);
|
|
return init.create();
|
|
}
|
|
|
|
ArrayData *ArrayData::CreateRef(CVarRef value) {
|
|
ArrayInit init(1);
|
|
init.setRef(value);
|
|
return init.create();
|
|
}
|
|
|
|
ArrayData *ArrayData::CreateRef(CVarRef name, CVarRef value) {
|
|
ArrayInit init(1);
|
|
// There is no toKey() call on name.
|
|
init.setRef(name, value, true);
|
|
return init.create();
|
|
}
|
|
|
|
ArrayData *ArrayData::nonSmartCopy() const {
|
|
throw FatalErrorException("nonSmartCopy not implemented.");
|
|
}
|
|
|
|
HOT_FUNC
|
|
void ArrayData::release() {
|
|
if (isHphpArray()) {
|
|
HphpArray* that = static_cast<HphpArray*>(this);
|
|
that->release();
|
|
return;
|
|
}
|
|
if (isSharedMap()) {
|
|
SharedMap* that = static_cast<SharedMap*>(this);
|
|
that->release();
|
|
return;
|
|
}
|
|
if (isPolicyArray()) {
|
|
auto that = static_cast<PolicyArray*>(this);
|
|
that->release();
|
|
return;
|
|
}
|
|
assert(m_kind == ArrayKind::kNameValueTableWrapper);
|
|
// NameValueTableWrapper: nop.
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// reads
|
|
|
|
Object ArrayData::toObject() const {
|
|
return Instance::FromArray(const_cast<ArrayData *>(this));
|
|
}
|
|
|
|
int ArrayData::compare(const ArrayData *v2) const {
|
|
assert(v2);
|
|
|
|
auto const count1 = size();
|
|
auto const count2 = v2->size();
|
|
if (count1 < count2) return -1;
|
|
if (count1 > count2) return 1;
|
|
if (count1 == 0) return 0;
|
|
|
|
// prevent circular referenced objects/arrays or deep ones
|
|
DECLARE_THREAD_INFO; check_recursion(info);
|
|
|
|
for (ArrayIter iter(this); iter; ++iter) {
|
|
auto key = iter.first();
|
|
if (!v2->exists(key)) return 1;
|
|
auto value1 = iter.second();
|
|
auto value2 = v2->get(key);
|
|
if (HPHP::more(value1, value2)) return 1;
|
|
if (HPHP::less(value1, value2)) return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool ArrayData::equal(const ArrayData *v2, bool strict) const {
|
|
assert(v2);
|
|
|
|
auto const count1 = size();
|
|
auto const count2 = v2->size();
|
|
if (count1 != count2) return false;
|
|
if (count1 == 0) return true;
|
|
|
|
// prevent circular referenced objects/arrays or deep ones
|
|
DECLARE_THREAD_INFO; check_recursion(info);
|
|
|
|
if (strict) {
|
|
for (ArrayIter iter1(this), iter2(v2); iter1; ++iter1, ++iter2) {
|
|
assert(iter2);
|
|
if (!same(iter1.first(), iter2.first())
|
|
|| !same(iter1.second(), iter2.secondRef())) return false;
|
|
}
|
|
} else {
|
|
for (ArrayIter iter(this); iter; ++iter) {
|
|
Variant key(iter.first());
|
|
if (!v2->exists(key)) return false;
|
|
if (!tvEqual(iter.second().asTypedValue(),
|
|
v2->get(key).asTypedValue())) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ArrayData *ArrayData::createLvalPtr(StringData* k, Variant *&ret, bool copy) {
|
|
throw FatalErrorException("Unimplemented ArrayData::createLvalPtr");
|
|
}
|
|
|
|
ArrayData *ArrayData::getLvalPtr(StringData* k, Variant *&ret, bool copy) {
|
|
throw FatalErrorException("Unimplemented ArrayData::getLvalPtr");
|
|
}
|
|
|
|
ArrayData *ArrayData::add(int64_t k, CVarRef v, bool copy) {
|
|
assert(!exists(k));
|
|
return set(k, v, copy);
|
|
}
|
|
|
|
ArrayData *ArrayData::add(StringData* k, CVarRef v, bool copy) {
|
|
assert(!exists(k));
|
|
return set(k, v, copy);
|
|
}
|
|
|
|
ArrayData *ArrayData::addLval(int64_t k, Variant *&ret, bool copy) {
|
|
assert(!exists(k));
|
|
return lval(k, ret, copy);
|
|
}
|
|
|
|
ArrayData *ArrayData::addLval(StringData* k, Variant *&ret, bool copy) {
|
|
assert(!exists(k));
|
|
return lval(k, ret, copy);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// stack and queue operations
|
|
|
|
ArrayData *ArrayData::pop(Variant &value) {
|
|
if (!empty()) {
|
|
ssize_t pos = iter_end();
|
|
value = getValue(pos);
|
|
return remove(getKey(pos), getCount() > 1);
|
|
}
|
|
value = uninit_null();
|
|
return this;
|
|
}
|
|
|
|
ArrayData *ArrayData::dequeue(Variant &value) {
|
|
if (!empty()) {
|
|
auto const pos = iter_begin();
|
|
value = getValue(pos);
|
|
ArrayData *ret = remove(getKey(pos), getCount() > 1);
|
|
|
|
// In PHP, array_shift() will cause all numerically key-ed values re-keyed
|
|
ret->renumber();
|
|
return ret;
|
|
}
|
|
value = uninit_null();
|
|
return this;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// MutableArrayIter related functions
|
|
|
|
void ArrayData::newFullPos(FullPos &fp) {
|
|
assert(!fp.getContainer());
|
|
fp.setContainer(this);
|
|
fp.setNext(strongIterators());
|
|
setStrongIterators(&fp);
|
|
fp.m_pos = m_pos;
|
|
}
|
|
|
|
void ArrayData::freeFullPos(FullPos &fp) {
|
|
assert(strongIterators() != 0 && fp.getContainer() == (ArrayData*)this);
|
|
// search for fp in our list, then remove it. Usually its the first one.
|
|
FullPos* p = strongIterators();
|
|
if (p == &fp) {
|
|
setStrongIterators(p->getNext());
|
|
fp.setContainer(nullptr);
|
|
return;
|
|
}
|
|
for (; p->getNext(); p = p->getNext()) {
|
|
if (p->getNext() == &fp) {
|
|
p->setNext(p->getNext()->getNext());
|
|
fp.setContainer(nullptr);
|
|
return;
|
|
}
|
|
}
|
|
// If the strong iterator list was empty or if fp could not be
|
|
// found in the strong iterator list, then we are in a bad state
|
|
assert(false);
|
|
}
|
|
|
|
bool ArrayData::validFullPos(const FullPos& fp) const {
|
|
assert(fp.getContainer() == (ArrayData*)this);
|
|
return false;
|
|
}
|
|
|
|
bool ArrayData::advanceFullPos(FullPos& fp) {
|
|
return false;
|
|
}
|
|
|
|
void ArrayData::freeStrongIterators() {
|
|
for (FullPosRange r(strongIterators()); !r.empty(); r.popFront()) {
|
|
r.front()->setContainer(nullptr);
|
|
}
|
|
setStrongIterators(0);
|
|
}
|
|
|
|
void ArrayData::moveStrongIterators(ArrayData* dest, ArrayData* src) {
|
|
for (FullPosRange r(src->strongIterators()); !r.empty(); r.popFront()) {
|
|
r.front()->setContainer(dest);
|
|
}
|
|
// move pointer to list and flag in one copy
|
|
dest->m_strongIterators = src->m_strongIterators;
|
|
src->m_strongIterators = 0;
|
|
}
|
|
|
|
CVarRef ArrayData::endRef() {
|
|
if (size_t(m_pos) < size_t(size())) {
|
|
return getValueRef(size() - 1);
|
|
}
|
|
throw FatalErrorException("invalid ArrayData::m_pos");
|
|
}
|
|
|
|
ArrayData* ArrayData::escalateForSort() {
|
|
if (getCount() > 1) {
|
|
return copy();
|
|
}
|
|
return this;
|
|
}
|
|
void ArrayData::ksort(int sort_flags, bool ascending) {
|
|
throw FatalErrorException("Unimplemented ArrayData::ksort");
|
|
}
|
|
void ArrayData::sort(int sort_flags, bool ascending) {
|
|
throw FatalErrorException("Unimplemented ArrayData::sort");
|
|
}
|
|
void ArrayData::asort(int sort_flags, bool ascending) {
|
|
throw FatalErrorException("Unimplemented ArrayData::asort");
|
|
}
|
|
void ArrayData::uksort(CVarRef cmp_function) {
|
|
throw FatalErrorException("Unimplemented ArrayData::uksort");
|
|
}
|
|
void ArrayData::usort(CVarRef cmp_function) {
|
|
throw FatalErrorException("Unimplemented ArrayData::usort");
|
|
}
|
|
void ArrayData::uasort(CVarRef cmp_function) {
|
|
throw FatalErrorException("Unimplemented ArrayData::uasort");
|
|
}
|
|
ArrayData* ArrayData::copyWithStrongIterators() const {
|
|
throw FatalErrorException("Unimplemented ArrayData::copyWithStrongIterators");
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Default implementation of position-based iterations.
|
|
|
|
Variant ArrayData::reset() { return value(m_pos = 0);}
|
|
Variant ArrayData::prev() { return value(--m_pos);}
|
|
Variant ArrayData::next() { return value(++m_pos);}
|
|
Variant ArrayData::end() { return value(m_pos = size() - 1);}
|
|
|
|
Variant ArrayData::key() const {
|
|
if (size_t(m_pos) < size_t(size())) {
|
|
return getKey(m_pos);
|
|
}
|
|
return uninit_null();
|
|
}
|
|
|
|
Variant ArrayData::value(int32_t &pos) const {
|
|
if (size_t(pos) < size_t(size())) {
|
|
return getValue(pos);
|
|
}
|
|
pos = ArrayData::invalid_index;
|
|
return false;
|
|
}
|
|
|
|
Variant ArrayData::current() const {
|
|
if (size_t(m_pos) < size_t(size())) {
|
|
return getValue(m_pos);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const StaticString
|
|
s_value("value"),
|
|
s_key("key");
|
|
|
|
Variant ArrayData::each() {
|
|
if (size_t(m_pos) < size_t(size())) {
|
|
Array ret;
|
|
Variant key(getKey(m_pos));
|
|
Variant value(getValue(m_pos));
|
|
ret.set(1, value);
|
|
ret.set(s_value, value);
|
|
ret.set(0, key);
|
|
ret.set(s_key, key);
|
|
++m_pos;
|
|
return ret;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
ssize_t ArrayData::iter_begin() const {
|
|
if (empty()) return ArrayData::invalid_index;
|
|
return 0;
|
|
}
|
|
|
|
ssize_t ArrayData::iter_end() const {
|
|
if (empty()) return ArrayData::invalid_index;
|
|
return size() - 1;
|
|
}
|
|
|
|
ssize_t ArrayData::iter_advance(ssize_t prev) const {
|
|
assert(prev >= 0 && prev < size());
|
|
ssize_t next = prev + 1;
|
|
if (next >= size()) return ArrayData::invalid_index;
|
|
return next;
|
|
}
|
|
|
|
ssize_t ArrayData::iter_rewind(ssize_t prev) const {
|
|
assert(prev >= 0 && prev < size());
|
|
ssize_t next = prev - 1;
|
|
if (next < 0) return ArrayData::invalid_index;
|
|
return next;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// helpers
|
|
|
|
void ArrayData::serializeImpl(VariableSerializer *serializer) const {
|
|
serializer->writeArrayHeader(size(), isVectorData());
|
|
for (ArrayIter iter(this); iter; ++iter) {
|
|
serializer->writeArrayKey(iter.first());
|
|
serializer->writeArrayValue(iter.secondRef());
|
|
}
|
|
serializer->writeArrayFooter();
|
|
}
|
|
|
|
void ArrayData::serialize(VariableSerializer *serializer,
|
|
bool skipNestCheck /* = false */) const {
|
|
if (size() == 0) {
|
|
serializer->writeArrayHeader(0, isVectorData());
|
|
serializer->writeArrayFooter();
|
|
return;
|
|
}
|
|
if (!skipNestCheck) {
|
|
if (serializer->incNestedLevel((void*)this)) {
|
|
serializer->writeOverflow((void*)this);
|
|
} else {
|
|
serializeImpl(serializer);
|
|
}
|
|
serializer->decNestedLevel((void*)this);
|
|
} else {
|
|
// If isObject, the array is temporary and we should not check or save
|
|
// its pointer.
|
|
serializeImpl(serializer);
|
|
}
|
|
}
|
|
|
|
bool ArrayData::hasInternalReference(PointerSet &vars,
|
|
bool ds /* = false */) const {
|
|
if (isSharedMap()) return false;
|
|
for (ArrayIter iter(this); iter; ++iter) {
|
|
CVarRef var = iter.secondRef();
|
|
if (var.isReferenced()) {
|
|
Variant *pvar = var.getRefData();
|
|
if (vars.find(pvar) != vars.end()) {
|
|
return true;
|
|
}
|
|
vars.insert(pvar);
|
|
}
|
|
if (var.isObject()) {
|
|
ObjectData *pobj = var.getObjectData();
|
|
if (vars.find(pobj) != vars.end()) {
|
|
return true;
|
|
}
|
|
vars.insert(pobj);
|
|
if (ds && pobj->instanceof(SystemLib::s_SerializableClass)) {
|
|
return true;
|
|
}
|
|
if (pobj->hasInternalReference(vars, ds)) {
|
|
return true;
|
|
}
|
|
} else if (var.isArray() &&
|
|
var.getArrayData()->hasInternalReference(vars, ds)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
CVarRef ArrayData::get(CVarRef k, bool error) const {
|
|
assert(IsValidKey(k));
|
|
auto const cell = k.asCell();
|
|
return isIntKey(cell) ? get(getIntKey(cell), error)
|
|
: get(getStringKey(cell), error);
|
|
}
|
|
|
|
void ArrayData::nvGetKey(TypedValue* out, ssize_t pos) {
|
|
Variant k = getKey(pos);
|
|
TypedValue* tv = k.asTypedValue();
|
|
// copy w/out clobbering out->_count.
|
|
out->m_type = tv->m_type;
|
|
out->m_data.num = tv->m_data.num;
|
|
if (tv->m_type != KindOfInt64) out->m_data.pstr->incRefCount();
|
|
}
|
|
|
|
TypedValue* ArrayData::nvGetValueRef(ssize_t pos) {
|
|
return const_cast<TypedValue*>(getValueRef(pos).asTypedValue());
|
|
}
|
|
|
|
TypedValue* ArrayData::nvGetCell(int64_t k) const {
|
|
TypedValue* tv = (TypedValue*)&get(k, false);
|
|
return LIKELY(tv != (TypedValue*)&null_variant) ? tvToCell(tv) :
|
|
nvGetNotFound(k);
|
|
}
|
|
|
|
TypedValue* ArrayData::nvGetCell(const StringData* key) const {
|
|
TypedValue* tv = (TypedValue*)&get(key, false);
|
|
return LIKELY(tv != (TypedValue*)&null_variant) ? tvToCell(tv) :
|
|
nvGetNotFound(key);
|
|
}
|
|
|
|
CVarRef ArrayData::getNotFound(int64_t k) {
|
|
raise_notice("Undefined index: %" PRId64, k);
|
|
return null_variant;
|
|
}
|
|
|
|
CVarRef ArrayData::getNotFound(const StringData* k) {
|
|
raise_notice("Undefined index: %s", k->data());
|
|
return null_variant;
|
|
}
|
|
|
|
CVarRef ArrayData::getNotFound(int64_t k, bool error) const {
|
|
return error && m_kind != ArrayKind::kNameValueTableWrapper ? getNotFound(k) :
|
|
null_variant;
|
|
}
|
|
|
|
CVarRef ArrayData::getNotFound(const StringData* k, bool error) const {
|
|
return error && m_kind != ArrayKind::kNameValueTableWrapper ? getNotFound(k) :
|
|
null_variant;
|
|
}
|
|
|
|
CVarRef ArrayData::getNotFound(CStrRef k) {
|
|
raise_notice("Undefined index: %s", k.data());
|
|
return null_variant;
|
|
}
|
|
|
|
CVarRef ArrayData::getNotFound(CVarRef k) {
|
|
raise_notice("Undefined index: %s", k.toString().data());
|
|
return null_variant;
|
|
}
|
|
|
|
TypedValue* ArrayData::nvGetNotFound(int64_t k) {
|
|
raise_notice("Undefined index: %" PRId64, k);
|
|
return (TypedValue*)&init_null_variant;
|
|
}
|
|
|
|
TypedValue* ArrayData::nvGetNotFound(const StringData* k) {
|
|
raise_notice("Undefined index: %s", k->data());
|
|
return (TypedValue*)&init_null_variant;
|
|
}
|
|
|
|
void ArrayData::dump() {
|
|
string out; dump(out); fwrite(out.c_str(), out.size(), 1, stdout);
|
|
}
|
|
|
|
void ArrayData::dump(std::string &out) {
|
|
VariableSerializer vs(VariableSerializer::Type::VarDump);
|
|
String ret(vs.serialize(Array(this), true));
|
|
out += "ArrayData(";
|
|
out += boost::lexical_cast<string>(_count);
|
|
out += "): ";
|
|
out += string(ret.data(), ret.size());
|
|
}
|
|
|
|
void ArrayData::dump(std::ostream &out) {
|
|
unsigned int i = 0;
|
|
for (ArrayIter iter(this); iter; ++iter, i++) {
|
|
VariableSerializer vs(VariableSerializer::Type::Serialize);
|
|
Variant key(iter.first());
|
|
out << i << " #### " << key.toString()->toCPPString() << " #### ";
|
|
Variant val(iter.second());
|
|
try {
|
|
String valS(vs.serialize(val, true));
|
|
out << valS->toCPPString();
|
|
} catch (const Exception &e) {
|
|
out << "Exception: " << e.what();
|
|
}
|
|
out << std::endl;
|
|
}
|
|
}
|
|
|
|
void ArrayData::getChildren(std::vector<TypedValue *> &out) {
|
|
if (isSharedMap()) {
|
|
SharedMap *sm = static_cast<SharedMap *>(this);
|
|
sm->getChildren(out);
|
|
return;
|
|
}
|
|
for (ssize_t pos = iter_begin();
|
|
pos != ArrayData::invalid_index;
|
|
pos = iter_advance(pos)) {
|
|
TypedValue *tv = nvGetValueRef(pos);
|
|
out.push_back(tv);
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|