diff --git a/bin/systemlib.php b/bin/systemlib.php index a8a013041..91519efea 100644 --- a/bin/systemlib.php +++ b/bin/systemlib.php @@ -2280,8 +2280,7 @@ class MappedIterator implements Iterator { $this->it->next(); } public function key() { - throw new RuntimeException( - "Call to undefined method MappedIterator::key()"); + return null; } public function current() { return ($this->fn)($this->it->current()); @@ -2377,8 +2376,7 @@ class FilteredIterator implements Iterator { } } public function key() { - throw new RuntimeException( - "Call to undefined method FilteredIterator::key()"); + return null; } public function current() { return $this->it->current(); @@ -2477,8 +2475,7 @@ class ZippedIterator implements Iterator { $this->it2->next(); } public function key() { - throw new RuntimeException( - "Call to undefined method ZippedIterator::key()"); + return null; } public function current() { return Pair {$this->it1->current(), $this->it2->current()}; @@ -2567,8 +2564,7 @@ class KeysIterator implements Iterator { $this->it->next(); } public function key() { - throw new RuntimeException( - "Call to undefined method KeysIterator::key()"); + return null; } public function current() { return $this->it->key(); @@ -2607,8 +2603,7 @@ class KVZippedIterator implements Iterator { $this->it->next(); } public function key() { - throw new RuntimeException( - "Call to undefined method KVZippedIterator::key()"); + return null; } public function current() { return Pair {$this->it->key(), $this->it->current()}; diff --git a/hphp/compiler/analysis/emitter.cpp b/hphp/compiler/analysis/emitter.cpp index a2c08399c..dd0b0809d 100644 --- a/hphp/compiler/analysis/emitter.cpp +++ b/hphp/compiler/analysis/emitter.cpp @@ -2894,6 +2894,8 @@ bool EmitterVisitor::visitImpl(ConstructPtr node) { cType = Collection::MapType; } else if (!strcasecmp(clsName->c_str(), "stablemap")) { cType = Collection::StableMapType; + } else if (!strcasecmp(clsName->c_str(), "set")) { + cType = Collection::SetType; } else if (!strcasecmp(clsName->c_str(), "pair")) { cType = Collection::PairType; if (nElms != 2) { @@ -2930,7 +2932,8 @@ bool EmitterVisitor::visitImpl(ConstructPtr node) { ExpressionPtr key = ap->getName(); if ((bool)key) { throw IncludeTimeFatalException(ap, - "Keys may not be specified for Vector initialization"); + "Keys may not be specified for Vector, Set, or Pair " + "initialization"); } visit(ap->getValue()); emitConvertToCell(e); diff --git a/hphp/compiler/expression/binary_op_expression.cpp b/hphp/compiler/expression/binary_op_expression.cpp index 688c362e3..9e6e8f7b1 100644 --- a/hphp/compiler/expression/binary_op_expression.cpp +++ b/hphp/compiler/expression/binary_op_expression.cpp @@ -71,6 +71,8 @@ BinaryOpExpression::BinaryOpExpression cType = Collection::MapType; } else if (strcasecmp(s.c_str(), "stablemap") == 0) { cType = Collection::StableMapType; + } else if (strcasecmp(s.c_str(), "set") == 0) { + cType = Collection::SetType; } else if (strcasecmp(s.c_str(), "pair") == 0) { cType = Collection::PairType; } diff --git a/hphp/idl/collections.idl.json b/hphp/idl/collections.idl.json index 30718478b..2d9661e8c 100644 --- a/hphp/idl/collections.idl.json +++ b/hphp/idl/collections.idl.json @@ -2112,6 +2112,516 @@ "consts": [ ] }, + { + "name": "Set", + "ifaces": [ + "MutableSet" + ], + "desc": "An unordered set-style collection.", + "flags": [ + "IsFinal", + "HasDocComment" + ], + "funcs": [ + { + "name": "__construct", + "flags": [ + "HasDocComment" + ], + "desc": "Returns a Set built from the values produced by the specified Iterable.", + "return": { + "type": null + }, + "args": [ + { + "name": "iterable", + "type": "Variant", + "value": "null" + } + ] + }, + { + "name": "isEmpty", + "flags": [ + "HasDocComment" + ], + "desc": "Returns true if the Set is empty, false otherwise.", + "return": { + "type": "Boolean" + }, + "args": [ + ] + }, + { + "name": "count", + "flags": [ + "HasDocComment" + ], + "desc": "Returns the number of values in the Set.", + "return": { + "type": "Int64" + }, + "args": [ + ] + }, + { + "name": "items", + "flags": [ + "HasDocComment" + ], + "desc": "Returns an Iterable that produces the values from this Set.", + "return": { + "type": "Object" + }, + "args": [ + ] + }, + { + "name": "view", + "flags": [ + "HasDocComment" + ], + "desc": "Returns a lazy iterable view of this Set.", + "return": { + "type": "Object" + }, + "args": [ + ] + }, + { + "name": "clear", + "flags": [ + "HasDocComment" + ], + "desc": "Removes all values from the Set.", + "return": { + "type": "Object" + }, + "args": [ + ] + }, + { + "name": "contains", + "flags": [ + "HasDocComment" + ], + "desc": "Returns true if the specified value is present in the Set, returns false otherwise.", + "return": { + "type": "Boolean" + }, + "args": [ + { + "name": "val", + "type": "Variant" + } + ] + }, + { + "name": "remove", + "flags": [ + "HasDocComment" + ], + "desc": "Removes the specified value from this Set.", + "return": { + "type": "Object" + }, + "args": [ + { + "name": "val", + "type": "Variant" + } + ] + }, + { + "name": "discard", + "flags": [ + "HasDocComment" + ], + "desc": "Removes the specified value from this Set.", + "return": { + "type": "Object" + }, + "args": [ + { + "name": "val", + "type": "Variant" + } + ] + }, + { + "name": "add", + "flags": [ + "HasDocComment" + ], + "desc": "Adds the specified value to this Set.", + "return": { + "type": "Object" + }, + "args": [ + { + "name": "val", + "type": "Variant" + } + ] + }, + { + "name": "addAll", + "flags": [ + "HasDocComment" + ], + "desc": "Adds the values produced by the specified Iterable to this Set.", + "return": { + "type": "Object" + }, + "args": [ + { + "name": "iterable", + "type": "Variant" + } + ] + }, + { + "name": "toArray", + "flags": [ + "HasDocComment" + ], + "desc": "Returns an array built from the values from this Set.", + "return": { + "type": "VariantMap" + }, + "args": [ + ] + }, + { + "name": "getIterator", + "flags": [ + "HasDocComment" + ], + "desc": "Returns an iterator that points to beginning of this Set.", + "return": { + "type": "Object" + }, + "args": [ + ] + }, + { + "name": "map", + "flags": [ + "HasDocComment" + ], + "desc": "Returns an Iterable of the values produced by applying the specified callback on the values of this Set.", + "return": { + "type": "Object" + }, + "args": [ + { + "name": "callback", + "type": "Variant" + } + ] + }, + { + "name": "filter", + "flags": [ + "HasDocComment" + ], + "desc": "Returns a Iterable of all the values from this Set for which the specified callback returns true.", + "return": { + "type": "Object" + }, + "args": [ + { + "name": "callback", + "type": "Variant" + } + ] + }, + { + "name": "zip", + "flags": [ + "HasDocComment" + ], + "desc": "Returns a Iterable produced by combined the specified Iterables pair-wise.", + "return": { + "type": "Object" + }, + "args": [ + { + "name": "iterable", + "type": "Variant" + } + ] + }, + { + "name": "difference", + "flags": [ + "HasDocComment" + ], + "return": { + "type": "Object" + }, + "args": [ + { + "name": "iterable", + "type": "Variant" + } + ] + }, + { + "name": "updateFromArrayValues", + "flags": [ + "HasDocComment" + ], + "return": { + "type": "Object" + }, + "args": [ + { + "name": "arr", + "type": "Variant" + } + ] + }, + { + "name": "updateFromIterableValues", + "flags": [ + "HasDocComment" + ], + "return": { + "type": "Object" + }, + "args": [ + { + "name": "iterable", + "type": "Variant" + } + ] + }, + { + "name": "__toString", + "return": { + "type": "String" + }, + "flags": [ + ], + "args": [ + ] + }, + { + "name": "__get", + "flags": [ + "HasDocComment" + ], + "return": { + "type": "Variant" + }, + "args": [ + { + "name": "name", + "type": "Variant" + } + ] + }, + { + "name": "__set", + "flags": [ + "HasDocComment" + ], + "return": { + "type": "Variant" + }, + "args": [ + { + "name": "name", + "type": "Variant" + }, + { + "name": "value", + "type": "Variant" + } + ] + }, + { + "name": "__isset", + "flags": [ + "HasDocComment" + ], + "return": { + "type": "Boolean" + }, + "args": [ + { + "name": "name", + "type": "Variant" + } + ] + }, + { + "name": "__unset", + "flags": [ + "HasDocComment" + ], + "return": { + "type": "Variant" + }, + "args": [ + { + "name": "name", + "type": "Variant" + } + ] + }, + { + "name": "fromItems", + "flags": [ + "IsStatic", + "HasDocComment" + ], + "desc": "Returns a Set built from the values produced by the specified Iterable.", + "return": { + "type": "Object" + }, + "args": [ + { + "name": "iterable", + "type": "Variant" + } + ] + }, + { + "name": "fromArray", + "flags": [ + "IsStatic", + "HasDocComment" + ], + "desc": "Returns a Set built from the values from the specified array.", + "return": { + "type": "Object" + }, + "args": [ + { + "name": "arr", + "type": "Variant" + } + ] + }, + { + "name": "fromArrays", + "flags": [ + "IsStatic", + "VariableArguments", + "HasDocComment" + ], + "desc": "Returns a Set built from the values from the specified arrays.", + "return": { + "type": "Object" + }, + "args": [ + ] + }, + { + "name": "fromIterableValues", + "flags": [ + "IsStatic", + "HasDocComment" + ], + "return": { + "type": "Object" + }, + "args": [ + { + "name": "iterable", + "type": "Variant" + } + ] + } + ], + "consts": [ + ] + }, + { + "name": "SetIterator", + "ifaces": [ + "Iterator" + ], + "desc": "An iterator implementation for iterating over a Set.", + "flags": [ + "IsFinal", + "HasDocComment" + ], + "funcs": [ + { + "name": "__construct", + "return": { + "type": null + }, + "flags": [ + ], + "args": [ + ] + }, + { + "name": "current", + "desc": "Returns the current value that the iterator points to.", + "flags": [ + "HasDocComment" + ], + "return": { + "type": "Variant" + }, + "args": [ + ] + }, + { + "name": "key", + "flags": [ + ], + "return": { + "type": "Variant" + }, + "args": [ + ] + }, + { + "name": "valid", + "desc": "Returns true if the iterator points to a valid value, returns false otherwise.", + "flags": [ + "HasDocComment" + ], + "return": { + "type": "Boolean" + }, + "args": [ + ] + }, + { + "name": "next", + "desc": "Advance this iterator forward one position.", + "flags": [ + "HasDocComment" + ], + "return": { + "type": null + }, + "args": [ + ] + }, + { + "name": "rewind", + "desc": "Move this iterator back to the first position.", + "flags": [ + "HasDocComment" + ], + "return": { + "type": null + }, + "args": [ + ] + } + ], + "consts": [ + ] + }, { "name": "Pair", "ifaces": [ @@ -2416,4 +2926,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/hphp/runtime/base/array/array_iterator.cpp b/hphp/runtime/base/array/array_iterator.cpp index ec2acfc83..09dc17f8d 100644 --- a/hphp/runtime/base/array/array_iterator.cpp +++ b/hphp/runtime/base/array/array_iterator.cpp @@ -101,6 +101,12 @@ void ArrayIter::objInit(ObjectData *obj) { m_pos = smp->iter_begin(); break; } + case Collection::SetType: { + c_Set* st = getSet(); + m_version = st->getVersion(); + m_pos = st->iter_begin(); + break; + } case Collection::PairType: { m_pos = 0; break; @@ -157,6 +163,9 @@ bool ArrayIter::endHelper() { case Collection::StableMapType: { return m_pos == 0; } + case Collection::SetType: { + return m_pos == 0; + } case Collection::PairType: { return m_pos >= getPair()->t_count(); } @@ -191,6 +200,15 @@ void ArrayIter::nextHelper() { m_pos = smp->iter_next(m_pos); return; } + case Collection::SetType: { + assert(m_pos != 0); + c_Set* st = getSet(); + if (UNLIKELY(m_version != st->getVersion())) { + throw_collection_modified(); + } + m_pos = st->iter_next(m_pos); + return; + } case Collection::PairType: { m_pos++; return; @@ -222,6 +240,9 @@ Variant ArrayIter::firstHelper() { } return smp->iter_key(m_pos); } + case Collection::SetType: { + return uninit_null(); + } case Collection::PairType: { return m_pos; } @@ -262,6 +283,13 @@ Variant ArrayIter::second() { } return smp->iter_value(m_pos); } + case Collection::SetType: { + c_Set* st = getSet(); + if (UNLIKELY(m_version != st->getVersion())) { + throw_collection_modified(); + } + return st->iter_value(m_pos); + } case Collection::PairType: { return tvAsCVarRef(getPair()->at(m_pos)); } @@ -298,6 +326,14 @@ void ArrayIter::secondHelper(Variant& v) { v = smp->iter_value(m_pos); break; } + case Collection::SetType: { + c_Set* st = getSet(); + if (UNLIKELY(m_version != st->getVersion())) { + throw_collection_modified(); + } + v = st->iter_value(m_pos); + break; + } case Collection::PairType: { v = tvAsCVarRef(getPair()->at(m_pos)); break; diff --git a/hphp/runtime/base/array/array_iterator.h b/hphp/runtime/base/array/array_iterator.h index a2e6f37bc..1c292e5e1 100644 --- a/hphp/runtime/base/array/array_iterator.h +++ b/hphp/runtime/base/array/array_iterator.h @@ -30,6 +30,7 @@ struct TypedValue; class c_Vector; class c_Map; class c_StableMap; +class c_Set; class c_Pair; struct Iter; @@ -192,6 +193,10 @@ class ArrayIter { assert(hasCollection() && getCollectionType() == Collection::StableMapType); return (c_StableMap*)((intptr_t)m_obj & ~1); } + c_Set* getSet() { + assert(hasCollection() && getCollectionType() == Collection::SetType); + return (c_Set*)((intptr_t)m_obj & ~1); + } c_Pair* getPair() { assert(hasCollection() && getCollectionType() == Collection::PairType); return (c_Pair*)((intptr_t)m_obj & ~1); diff --git a/hphp/runtime/base/object_data.h b/hphp/runtime/base/object_data.h index b4974ebcd..2fadc3ce3 100644 --- a/hphp/runtime/base/object_data.h +++ b/hphp/runtime/base/object_data.h @@ -68,6 +68,7 @@ class ObjectData : public CountableNF { VectorAttrInit = (Collection::VectorType << 13), MapAttrInit = (Collection::MapType << 13), StableMapAttrInit = (Collection::StableMapType << 13), + SetAttrInit = (Collection::SetType << 13), PairAttrInit = (Collection::PairType << 13), }; diff --git a/hphp/runtime/base/types.h b/hphp/runtime/base/types.h index 57c8fd448..8083a386f 100644 --- a/hphp/runtime/base/types.h +++ b/hphp/runtime/base/types.h @@ -321,8 +321,9 @@ enum Type { VectorType = 1, MapType = 2, StableMapType = 3, - PairType = 4, - MaxNumTypes = 5 + SetType = 4, + PairType = 5, + MaxNumTypes = 6 }; } diff --git a/hphp/runtime/base/variable_serializer.cpp b/hphp/runtime/base/variable_serializer.cpp index 8ff001f56..d322e5eb7 100644 --- a/hphp/runtime/base/variable_serializer.cpp +++ b/hphp/runtime/base/variable_serializer.cpp @@ -708,7 +708,6 @@ void VariableSerializer::writeArrayKey(Variant key) { m_buf->append("]=>\n"); break; case APCSerialize: - assert(!info.is_object); case Serialize: case DebuggerSerialize: write(key); @@ -758,6 +757,36 @@ void VariableSerializer::writeCollectionKey(CVarRef key) { writeArrayKey(key); } +void VariableSerializer::writeCollectionKeylessPrefix() { + ArrayInfo &info = m_arrayInfos.back(); + switch (m_type) { + case PrintR: + case VarExport: + case PHPOutput: + indent(); + break; + case VarDump: + case DebugDump: + case APCSerialize: + case Serialize: + case DebuggerSerialize: + break; + case JSON: + case DebuggerDump: + if (!info.first_element) { + m_buf->append(','); + } + if (m_type == JSON && m_option & k_JSON_PRETTY_PRINT) { + m_buf->append("\n"); + indent(); + } + break; + default: + assert(false); + break; + } +} + void VariableSerializer::writeArrayValue(CVarRef value) { // Do not count referenced values after the first if ((m_type == Serialize || m_type == APCSerialize || diff --git a/hphp/runtime/base/variable_serializer.h b/hphp/runtime/base/variable_serializer.h index eb09c0d23..348d79073 100644 --- a/hphp/runtime/base/variable_serializer.h +++ b/hphp/runtime/base/variable_serializer.h @@ -53,7 +53,7 @@ public: /** * Constructor and destructor. */ - VariableSerializer(Type type, int option = 0, int maxRecur = 3); + explicit VariableSerializer(Type type, int option = 0, int maxRecur = 3); ~VariableSerializer() { if (m_arrayIds) delete m_arrayIds; } @@ -91,6 +91,7 @@ public: void writeArrayKey(Variant key); void writeArrayValue(CVarRef value); void writeCollectionKey(CVarRef key); + void writeCollectionKeylessPrefix(); void writeArrayFooter(); void writeSerializableObject(CStrRef clsname, CStrRef serialized); diff --git a/hphp/runtime/ext/ext_collections.cpp b/hphp/runtime/ext/ext_collections.cpp index 1de974a87..7c50b8131 100644 --- a/hphp/runtime/ext/ext_collections.cpp +++ b/hphp/runtime/ext/ext_collections.cpp @@ -27,7 +27,10 @@ namespace HPHP { /////////////////////////////////////////////////////////////////////////////// -static void throwIntOOB(int64_t key, bool isVector = false) { +static void throwIntOOB(int64_t key, bool isVector = false) ATTRIBUTE_COLD + ATTRIBUTE_NORETURN; + +void throwIntOOB(int64_t key, bool isVector /* = false */) { static const size_t reserveSize = 50; String msg(reserveSize, ReserveString); char* buf = msg.mutableSlice().ptr; @@ -39,7 +42,9 @@ static void throwIntOOB(int64_t key, bool isVector = false) { throw e; } -static void throwStrOOB(StringData* key) { +static void throwStrOOB(StringData* key) ATTRIBUTE_COLD ATTRIBUTE_NORETURN; + +void throwStrOOB(StringData* key) { const size_t maxDisplaySize = 20; const char* dots = "..."; size_t dotsSize = strlen(dots); @@ -609,9 +614,6 @@ Object c_Vector::ti_fromitems(const char* cls, CVarRef iterable) { ArrayIter iter = getArrayIterHelper(iterable, sz); c_Vector* target; Object ret = target = NEWOBJ(c_Vector)(); - if (sz) { - target->reserve(sz); - } for (uint i = 0; iter; ++i, ++iter) { Variant v = iter.second(); TypedValue* tv = tvToCell(v.asTypedValue()); @@ -1095,7 +1097,7 @@ Object c_Map::t_add(CVarRef val) { Object c_Map::t_addall(CVarRef iterable) { size_t sz; ArrayIter iter = getArrayIterHelper(iterable, sz); - reserve(m_size + sz); + reserve(std::max(sz, size_t(m_size))); for (; iter; ++iter) { Variant v = iter.second(); TypedValue* tv = cvarToCell(&v); @@ -1638,9 +1640,9 @@ void c_Map::add(TypedValue* val) { assert(tvKey->m_type != KindOfRef); assert(tvValue->m_type != KindOfRef); if (tvKey->m_type == KindOfInt64) { - updateImpl(tvKey->m_data.num, tvValue); + update(tvKey->m_data.num, tvValue); } else if (IS_STRING_TYPE(tvKey->m_type)) { - updateImpl(tvKey->m_data.pstr, tvValue); + update(tvKey->m_data.pstr, tvValue); } else { throwBadKeyType(); } @@ -1734,6 +1736,8 @@ c_Map::Bucket* c_Map::findForInsert(const char* k, int len, FIND_FOR_INSERT_BODY(prehash, hitStringKey(p, k, len, STRING_HASH(prehash))); } +// findForNewInsert() is only safe to use if you know for sure that the +// key is not already present in the Map. inline ALWAYS_INLINE c_Map::Bucket* c_Map::findForNewInsert(size_t h0) const { size_t tableMask = m_nLastSlot; @@ -1757,17 +1761,11 @@ c_Map::Bucket* c_Map::findForNewInsert(size_t h0) const { #undef FIND_BODY #undef FIND_FOR_INSERT_BODY -template -bool c_Map::updateImpl(int64_t h, TypedValue* data) { +bool c_Map::update(int64_t h, TypedValue* data) { assert(data->m_type != KindOfRef); Bucket* p = findForInsert(h); assert(p); if (p->validValue()) { - if (throwIfExists) { - Object e(SystemLib::AllocInvalidArgumentExceptionObject( - "An element with the same key already exists")); - throw e; - } tvRefcountedIncRef(data); tvRefcountedDecRef(&p->data); p->data.m_data.num = data->m_data.num; @@ -1778,7 +1776,7 @@ bool c_Map::updateImpl(int64_t h, TypedValue* data) { ++m_size; if (!p->tombstone()) { if (UNLIKELY(++m_load >= computeMaxLoad())) { - grow(); + adjustCapacity(); p = findForInsert(h); assert(p); } @@ -1790,17 +1788,11 @@ bool c_Map::updateImpl(int64_t h, TypedValue* data) { return true; } -template -bool c_Map::updateImpl(StringData *key, TypedValue* data) { +bool c_Map::update(StringData *key, TypedValue* data) { strhash_t h = key->hash(); Bucket* p = findForInsert(key->data(), key->size(), h); assert(p); if (p->validValue()) { - if (throwIfExists) { - Object e(SystemLib::AllocInvalidArgumentExceptionObject( - "An element with the same key already exists")); - throw e; - } tvRefcountedIncRef(data); tvRefcountedDecRef(&p->data); p->data.m_data.num = data->m_data.num; @@ -1811,7 +1803,7 @@ bool c_Map::updateImpl(StringData *key, TypedValue* data) { ++m_size; if (!p->tombstone()) { if (UNLIKELY(++m_load >= computeMaxLoad())) { - grow(); + adjustCapacity(); p = findForInsert(key->data(), key->size(), h); assert(p); } @@ -1835,12 +1827,12 @@ void c_Map::erase(Bucket* p) { } p->data.m_type = (DataType)KindOfTombstone; if (m_size < computeMinElements() && m_size) { - grow(); + adjustCapacity(); } } } -void c_Map::growImpl(int64_t sz) { +void c_Map::adjustCapacityImpl(int64_t sz) { ++m_version; if (sz < 2) { if (sz <= 0) return; @@ -2287,7 +2279,7 @@ Object c_StableMap::t_add(CVarRef val) { Object c_StableMap::t_addall(CVarRef iterable) { size_t sz; ArrayIter iter = getArrayIterHelper(iterable, sz); - reserve(m_size + sz); + reserve(std::max(sz, size_t(m_size))); for (; iter; ++iter) { Variant v = iter.second(); TypedValue* tv = cvarToCell(&v); @@ -2824,9 +2816,9 @@ void c_StableMap::add(TypedValue* val) { assert(tvKey->m_type != KindOfRef); assert(tvValue->m_type != KindOfRef); if (tvKey->m_type == KindOfInt64) { - updateImpl(tvKey->m_data.num, tvValue); + update(tvKey->m_data.num, tvValue); } else if (IS_STRING_TYPE(tvKey->m_type)) { - updateImpl(tvKey->m_data.pstr, tvValue); + update(tvKey->m_data.pstr, tvValue); } else { throwBadKeyType(); } @@ -2887,15 +2879,9 @@ c_StableMap::Bucket** c_StableMap::findForErase(const char* k, int len, return nullptr; } -template -bool c_StableMap::updateImpl(int64_t h, TypedValue* data) { +bool c_StableMap::update(int64_t h, TypedValue* data) { Bucket* p = find(h); if (p) { - if (throwIfExists) { - Object e(SystemLib::AllocInvalidArgumentExceptionObject( - "An element with the same key already exists")); - throw e; - } tvRefcountedIncRef(data); tvRefcountedDecRef(&p->data); p->data.m_data.num = data->m_data.num; @@ -2904,7 +2890,7 @@ bool c_StableMap::updateImpl(int64_t h, TypedValue* data) { } ++m_version; if (++m_size > m_nTableSize) { - grow(); + adjustCapacity(); } p = NEW(Bucket)(data); p->setIntKey(h); @@ -2915,16 +2901,10 @@ bool c_StableMap::updateImpl(int64_t h, TypedValue* data) { return true; } -template -bool c_StableMap::updateImpl(StringData *key, TypedValue* data) { +bool c_StableMap::update(StringData *key, TypedValue* data) { strhash_t h = key->hash(); Bucket* p = find(key->data(), key->size(), h); if (p) { - if (throwIfExists) { - Object e(SystemLib::AllocInvalidArgumentExceptionObject( - "An element with the same key already exists")); - throw e; - } tvRefcountedIncRef(data); tvRefcountedDecRef(&p->data); p->data.m_data.num = data->m_data.num; @@ -2933,7 +2913,7 @@ bool c_StableMap::updateImpl(StringData *key, TypedValue* data) { } ++m_version; if (++m_size > m_nTableSize) { - grow(); + adjustCapacity(); } p = NEW(Bucket)(data); p->setStrKey(key, h); @@ -2969,7 +2949,7 @@ void c_StableMap::erase(Bucket** prev) { } } -void c_StableMap::growImpl(int64_t sz) { +void c_StableMap::adjustCapacityImpl(int64_t sz) { ++m_version; if (sz < 4) { if (sz <= 0) return; @@ -3437,6 +3417,796 @@ void c_StableMapIterator::t_rewind() { /////////////////////////////////////////////////////////////////////////////// +static const char emptySetSlot[sizeof(c_Set::Bucket)] = { 0 }; + +c_Set::c_Set(VM::Class* cb) : + ExtObjectDataFlags(cb), + m_size(0), m_load(0), m_nLastSlot(0), m_version(0) { + m_data = (Bucket*)emptySetSlot; +} + +c_Set::~c_Set() { + deleteBuckets(); + freeData(); +} + +void c_Set::freeData() { + if (m_data != (Bucket*)emptySetSlot) { + smart_free(m_data); + } + m_data = (Bucket*)emptySetSlot; +} + +void c_Set::deleteBuckets() { + if (!m_size) return; + for (uint i = 0; i <= m_nLastSlot; ++i) { + Bucket& p = m_data[i]; + if (p.validValue()) { + tvRefcountedDecRef(&p.data); + } + } +} + +void c_Set::t___construct(CVarRef iterable /* = null_variant */) { + if (!iterable.isInitialized()) { + return; + } + size_t sz; + ArrayIter iter = getArrayIterHelper(iterable, sz); + for (; iter; ++iter) { + Variant v = iter.second(); + if (v.isInteger()) { + update(v.toInt64()); + } else if (v.isString()) { + update(v.getStringData()); + } else { + throwBadValueType(); + } + } +} + +Array c_Set::toArrayImpl() const { + ArrayInit ai(m_size, ArrayInit::vectorInit); + for (uint i = 0; i <= m_nLastSlot; ++i) { + Bucket& p = m_data[i]; + if (p.validValue()) { + ai.set(tvAsCVarRef(&p.data)); + } + } + return ai.create(); +} + +Array c_Set::o_toArray() const { + check_collection_cast_to_array(); + return toArrayImpl(); +} + +ObjectData* c_Set::clone() { + ObjectData* obj = ObjectData::clone(); + auto target = static_cast(obj); + + if (!m_size) return obj; + + assert(m_nLastSlot != 0); + target->m_size = m_size; + target->m_load = m_load; + target->m_nLastSlot = m_nLastSlot; + target->m_data = (Bucket*)smart_malloc(numSlots() * sizeof(Bucket)); + memcpy(target->m_data, m_data, numSlots() * sizeof(Bucket)); + + for (uint i = 0; i <= m_nLastSlot; ++i) { + Bucket& p = m_data[i]; + if (p.validValue()) { + tvRefcountedIncRef(&p.data); + } + } + + return obj; +} + +Object c_Set::t_add(CVarRef val) { + TypedValue* tv = cvarToCell(&val); + add(tv); + return this; +} + +Object c_Set::t_addall(CVarRef iterable) { + size_t sz; + ArrayIter iter = getArrayIterHelper(iterable, sz); + for (; iter; ++iter) { + Variant v = iter.second(); + TypedValue* tv = cvarToCell(&v); + add(tv); + } + return this; +} + +Object c_Set::t_clear() { + deleteBuckets(); + freeData(); + m_size = 0; + m_load = 0; + m_nLastSlot = 0; + m_data = (Bucket*)emptySetSlot; + return this; +} + +bool c_Set::t_isempty() { + return (m_size == 0); +} + +int64_t c_Set::t_count() { + return m_size; +} + +Object c_Set::t_items() { + return SystemLib::AllocIterableViewObject(this); +} + +Object c_Set::t_view() { + return SystemLib::AllocIterableViewObject(this); +} + +bool c_Set::t_contains(CVarRef key) { + DataType t = key.getType(); + if (t == KindOfInt64) { + return contains(key.toInt64()); + } + if (IS_STRING_TYPE(t)) { + return contains(key.getStringData()); + } + throwBadValueType(); + return false; +} + +Object c_Set::t_remove(CVarRef key) { + DataType t = key.getType(); + if (t == KindOfInt64) { + remove(key.toInt64()); + } else if (IS_STRING_TYPE(t)) { + remove(key.getStringData()); + } else { + throwBadValueType(); + } + return this; +} + +Array c_Set::t_toarray() { + return toArrayImpl(); +} + +Object c_Set::t_getiterator() { + c_SetIterator* it = NEWOBJ(c_SetIterator)(); + it->m_obj = this; + it->m_pos = iter_begin(); + it->m_version = getVersion(); + return it; +} + +Object c_Set::t_map(CVarRef callback) { + CallCtx ctx; + vm_decode_function(callback, nullptr, false, ctx); + if (!ctx.func) { + Object e(SystemLib::AllocInvalidArgumentExceptionObject( + "Parameter must be a valid callback")); + throw e; + } + c_Set* st; + Object obj = st = NEWOBJ(c_Set)(); + if (!m_size) return obj; + assert(m_nLastSlot != 0); + st->m_size = m_size; + st->m_load = m_load; + st->m_nLastSlot = 0; + st->m_data = (Bucket*)smart_malloc(numSlots() * sizeof(Bucket)); + // We need to zero out the first slot in case an exception + // is thrown during the first iteration, because ~c_Set() + // will decRef all slots up to (and including) m_nLastSlot. + st->m_data[0].data.m_type = (DataType)0; + for (uint i = 0; i <= m_nLastSlot; st->m_nLastSlot = i++) { + Bucket& p = m_data[i]; + Bucket& np = st->m_data[i]; + if (!p.validValue()) { + np.data.m_type = p.data.m_type; + continue; + } + TypedValue* tv = &np.data; + int32_t version = m_version; + g_vmContext->invokeFunc(tv, ctx, CREATE_VECTOR1(tvAsCVarRef(&p.data))); + if (UNLIKELY(version != m_version)) { + tvRefcountedDecRef(tv); + throw_collection_modified(); + } + np.data.hash() = p.data.hash(); + } + return obj; +} + +Object c_Set::t_filter(CVarRef callback) { + CallCtx ctx; + vm_decode_function(callback, nullptr, false, ctx); + if (!ctx.func) { + Object e(SystemLib::AllocInvalidArgumentExceptionObject( + "Parameter must be a valid callback")); + throw e; + } + c_Set* st; + Object obj = st = NEWOBJ(c_Set)(); + if (!m_size) return obj; + for (uint i = 0; i <= m_nLastSlot; ++i) { + Bucket& p = m_data[i]; + if (!p.validValue()) continue; + Variant ret; + int32_t version = m_version; + g_vmContext->invokeFunc(ret.asTypedValue(), ctx, + CREATE_VECTOR1(tvAsCVarRef(&p.data))); + if (UNLIKELY(version != m_version)) { + throw_collection_modified(); + } + if (!ret.toBoolean()) continue; + if (p.hasInt()) { + st->update(p.data.m_data.num); + } else { + assert(p.hasStr()); + st->update(p.data.m_data.pstr); + } + } + return obj; +} + +Object c_Set::t_zip(CVarRef iterable) { + size_t sz; + ArrayIter iter = getArrayIterHelper(iterable, sz); + if (m_size && iter) { + // At present, Sets only support int values and string values, + // so if this Set is non empty and the iterable is non empty + // the zip operation will always fail + throwBadValueType(); + } + Object obj = NEWOBJ(c_Set)(); + return obj; +} + +Object c_Set::ti_fromitems(const char* cls, CVarRef iterable) { + size_t sz; + ArrayIter iter = getArrayIterHelper(iterable, sz); + c_Set* target; + Object ret = target = NEWOBJ(c_Set)(); + for (; iter; ++iter) { + Variant v = iter.second(); + if (v.isInteger()) { + target->update(v.toInt64()); + } else if (v.isString()) { + target->update(v.getStringData()); + } else { + throwBadValueType(); + } + } + return ret; +} + +Object c_Set::ti_fromarray(const char* cls, CVarRef arr) { + if (!arr.isArray()) { + Object e(SystemLib::AllocInvalidArgumentExceptionObject( + "Parameter arr must be an array")); + throw e; + } + c_Set* st; + Object ret = st = NEWOBJ(c_Set)(); + ArrayData* ad = arr.getArrayData(); + for (ssize_t pos = ad->iter_begin(); pos != ArrayData::invalid_index; + pos = ad->iter_advance(pos)) { + CVarRef v = ad->getValueRef(pos); + if (v.isInteger()) { + st->update(v.toInt64()); + } else if (v.isString()) { + st->update(v.getStringData()); + } else { + throwBadValueType(); + } + } + return ret; +} + +Object c_Set::t_discard(CVarRef key) { + return t_remove(key); +} + +Object c_Set::t_difference(CVarRef iterable) { + if (!iterable.isObject()) { + Object e(SystemLib::AllocInvalidArgumentExceptionObject( + "Parameter iterable must be an instance of Iterable")); + throw e; + } + size_t sz; + ArrayIter iter = getArrayIterHelper(iterable, sz); + for (; iter; ++iter) { + t_remove(iter.second()); + } + return this; +} + +Object c_Set::t_updatefromarrayvalues(CVarRef arr) { + if (!arr.isArray()) { + Object e(SystemLib::AllocInvalidArgumentExceptionObject( + "Parameter arr must be an array")); + throw e; + } + size_t sz; + ArrayIter iter = getArrayIterHelper(arr, sz); + for (; iter; ++iter) { + t_add(iter.second()); + } + return this; +} + +Object c_Set::t_updatefromiterablevalues(CVarRef iterable) { + if (!iterable.isObject()) { + Object e(SystemLib::AllocInvalidArgumentExceptionObject( + "Parameter iterable must be an instance of Iterable")); + throw e; + } + size_t sz; + ArrayIter iter = getArrayIterHelper(iterable, sz); + for (; iter; ++iter) { + t_add(iter.second()); + } + return this; +} + +Object c_Set::ti_fromarrays(const char* cls, int _argc, + CArrRef _argv /* = null_array */) { + c_Set* st; + Object ret = st = NEWOBJ(c_Set)(); + for (ArrayIter iter(_argv); iter; ++iter) { + Variant arr = iter.second(); + if (!arr.isArray()) { + Object e(SystemLib::AllocInvalidArgumentExceptionObject( + "Parameters must be arrays")); + throw e; + } + ArrayData* ad = arr.getArrayData(); + for (ssize_t pos = ad->iter_begin(); pos != ArrayData::invalid_index; + pos = ad->iter_advance(pos)) { + st->t_add(ad->getValueRef(pos)); + } + } + return ret; +} + +Object c_Set::ti_fromiterablevalues(const char* cls, CVarRef iterable) { + if (!iterable.isObject()) { + Object e(SystemLib::AllocInvalidArgumentExceptionObject( + "Parameter iterable must be an instance of Iterable")); + throw e; + } + size_t sz; + ArrayIter iter = getArrayIterHelper(iterable, sz); + c_Set* st; + Object ret = st = NEWOBJ(c_Set)(); + for (; iter; ++iter) { + st->t_add(iter.second()); + } + return ret; +} + +void c_Set::throwOOB(int64_t val) { + throwIntOOB(val); +} + +void c_Set::throwOOB(StringData* val) { + throwStrOOB(val); +} + +void c_Set::throwNoIndexAccess() { + Object e(SystemLib::AllocRuntimeExceptionObject( + "Cannot use object of type Set as array")); + throw e; +} + +void c_Set::add(TypedValue* val) { + if (val->m_type == KindOfInt64) { + update(val->m_data.num); + } else if (IS_STRING_TYPE(val->m_type)) { + update(val->m_data.pstr); + } else { + throwBadValueType(); + } +} + +#define STRING_HASH(x) (int32_t(x) | 0x80000000) + +bool inline hitString(const c_Set::Bucket* p, const char* k, + int len, int32_t hash) ALWAYS_INLINE; +bool inline hitString(const c_Set::Bucket* p, const char* k, + int len, int32_t hash) { + assert(p->validValue()); + if (p->hasInt()) return false; + const char* data = p->data.m_data.pstr->data(); + return data == k || (p->hash() == hash && + p->data.m_data.pstr->size() == len && + memcmp(data, k, len) == 0); +} + +bool inline hitInt(const c_Set::Bucket* p, int64_t ki) ALWAYS_INLINE; +bool inline hitInt(const c_Set::Bucket* p, int64_t ki) { + assert(p->validValue()); + return p->hasInt() && p->data.m_data.num == ki; +} + +#define FIND_BODY(h0, hit) \ + size_t tableMask = m_nLastSlot; \ + size_t probeIndex = size_t(h0) & tableMask; \ + Bucket* p = fetchBucket(probeIndex); \ + if (LIKELY(p->validValue() && (hit))) { \ + return p; \ + } \ + if (LIKELY(p->empty())) { \ + return nullptr; \ + } \ + for (size_t i = 1;; ++i) { \ + assert(i <= tableMask); \ + probeIndex = (probeIndex + i) & tableMask; \ + assert(((size_t(h0)+((i + i*i) >> 1)) & tableMask) == probeIndex); \ + p = fetchBucket(probeIndex); \ + if (p->validValue() && (hit)) { \ + return p; \ + } \ + if (p->empty()) { \ + return nullptr; \ + } \ + } + +#define FIND_FOR_INSERT_BODY(h0, hit) \ + size_t tableMask = m_nLastSlot; \ + size_t probeIndex = size_t(h0) & tableMask; \ + Bucket* p = fetchBucket(h0 & tableMask); \ + if (LIKELY((p->validValue() && (hit)) || \ + p->empty())) { \ + return p; \ + } \ + Bucket* ts = nullptr; \ + for (size_t i = 1;; ++i) { \ + if (UNLIKELY(p->tombstone() && !ts)) { \ + ts = p; \ + } \ + assert(i <= tableMask); \ + probeIndex = (probeIndex + i) & tableMask; \ + assert(((size_t(h0)+((i + i*i) >> 1)) & tableMask) == probeIndex); \ + p = fetchBucket(probeIndex); \ + if (LIKELY(p->validValue() && (hit))) { \ + return p; \ + } \ + if (LIKELY(p->empty())) { \ + if (LIKELY(!ts)) { \ + return p; \ + } \ + return ts; \ + } \ + } + +c_Set::Bucket* c_Set::find(int64_t h) const { + FIND_BODY(h, hitInt(p, h)); +} + +c_Set::Bucket* c_Set::find(const char* k, int len, strhash_t prehash) const { + FIND_BODY(prehash, hitString(p, k, len, STRING_HASH(prehash))); +} + +c_Set::Bucket* c_Set::findForInsert(int64_t h) const { + FIND_FOR_INSERT_BODY(h, hitInt(p, h)); +} + +c_Set::Bucket* c_Set::findForInsert(const char* k, int len, + strhash_t prehash) const { + FIND_FOR_INSERT_BODY(prehash, hitString(p, k, len, STRING_HASH(prehash))); +} + +// findForNewInsert() is only safe to use if you know for sure that the +// value is not already present in the Set. +inline ALWAYS_INLINE +c_Set::Bucket* c_Set::findForNewInsert(size_t h0) const { + size_t tableMask = m_nLastSlot; + size_t probeIndex = h0 & tableMask; + Bucket* p = fetchBucket(probeIndex); + if (LIKELY(p->empty())) { + return p; + } + for (size_t i = 1;; ++i) { + assert(i <= tableMask); + probeIndex = (probeIndex + i) & tableMask; + assert(((size_t(h0)+((i + i*i) >> 1)) & tableMask) == probeIndex); + p = fetchBucket(probeIndex); + if (LIKELY(p->empty())) { + return p; + } + } +} + +#undef STRING_HASH +#undef FIND_BODY +#undef FIND_FOR_INSERT_BODY + +void c_Set::update(int64_t h) { + Bucket* p = findForInsert(h); + assert(p); + if (p->validValue()) { + return; + } + ++m_version; + ++m_size; + if (!p->tombstone()) { + if (UNLIKELY(++m_load >= computeMaxLoad())) { + adjustCapacity(); + p = findForInsert(h); + assert(p); + } + } + p->setInt(h); +} + +void c_Set::update(StringData *key) { + strhash_t h = key->hash(); + Bucket* p = findForInsert(key->data(), key->size(), h); + assert(p); + if (p->validValue()) { + return; + } + ++m_version; + ++m_size; + if (!p->tombstone()) { + if (UNLIKELY(++m_load >= computeMaxLoad())) { + adjustCapacity(); + p = findForInsert(key->data(), key->size(), h); + assert(p); + } + } + p->setStr(key, h); +} + +void c_Set::erase(Bucket* p) { + if (!p) { + return; + } + if (p->validValue()) { + m_size--; + tvRefcountedDecRef(&p->data); + p->data.m_type = (DataType)KindOfTombstone; + if (m_size < computeMinElements() && m_size) { + adjustCapacity(); + } + } +} + +void c_Set::adjustCapacityImpl(int64_t sz) { + ++m_version; + if (sz < 2) { + if (sz <= 0) return; + sz = 2; + } + if (m_nLastSlot == 0) { + assert(m_data == (Bucket*)emptySetSlot); + m_nLastSlot = Util::roundUpToPowerOfTwo(sz << 1) - 1; + m_data = (Bucket*)smart_calloc(numSlots(), sizeof(Bucket)); + return; + } + size_t oldNumSlots = numSlots(); + m_nLastSlot = Util::roundUpToPowerOfTwo(sz << 1) - 1; + m_load = m_size; + Bucket* oldBuckets = m_data; + m_data = (Bucket*)smart_calloc(numSlots(), sizeof(Bucket)); + for (uint i = 0; i < oldNumSlots; ++i) { + Bucket* p = &oldBuckets[i]; + if (p->validValue()) { + Bucket* np = + findForNewInsert(p->hasInt() ? p->data.m_data.num : p->hash()); + memcpy(np, p, sizeof(Bucket)); + } + } + smart_free(oldBuckets); +} + +ssize_t c_Set::iter_begin() const { + if (!m_size) return 0; + for (uint i = 0; i <= m_nLastSlot; ++i) { + Bucket* p = fetchBucket(i); + if (p->validValue()) { + return reinterpret_cast(p); + } + } + return 0; +} + +ssize_t c_Set::iter_next(ssize_t pos) const { + if (pos == 0) { + return 0; + } + Bucket* p = reinterpret_cast(pos); + Bucket* pLast = fetchBucket(m_nLastSlot); + ++p; + while (p <= pLast) { + if (p->validValue()) { + return reinterpret_cast(p); + } + ++p; + } + return 0; +} + +ssize_t c_Set::iter_prev(ssize_t pos) const { + if (pos == 0) { + return 0; + } + Bucket* p = reinterpret_cast(pos); + Bucket* pStart = m_data; + --p; + while (p >= pStart) { + if (p->validValue()) { + return reinterpret_cast(p); + } + --p; + } + return 0; +} + +Variant c_Set::iter_value(ssize_t pos) const { + assert(pos); + Bucket* p = reinterpret_cast(pos); + return tvAsCVarRef(&p->data); +} + +void c_Set::throwBadValueType() { + Object e(SystemLib::AllocInvalidArgumentExceptionObject( + "Only integer values and string values may be used with Sets")); + throw e; +} + +void c_Set::Bucket::dump() { + if (!validValue()) { + printf("c_Set::Bucket: %s\n", (empty() ? "empty" : "tombstone")); + return; + } + printf("c_Set::Bucket: %d\n", hash()); + tvAsCVarRef(&data).dump(); +} + +TypedValue* c_Set::OffsetGet(ObjectData* obj, TypedValue* key) { + c_Set::throwNoIndexAccess(); +} + +void c_Set::OffsetSet(ObjectData* obj, TypedValue* key, TypedValue* val) { + c_Set::throwNoIndexAccess(); +} + +bool c_Set::OffsetIsset(ObjectData* obj, TypedValue* key) { + c_Set::throwNoIndexAccess(); +} + +bool c_Set::OffsetEmpty(ObjectData* obj, TypedValue* key) { + c_Set::throwNoIndexAccess(); +} + +bool c_Set::OffsetContains(ObjectData* obj, TypedValue* key) { + c_Set::throwNoIndexAccess(); +} + +void c_Set::OffsetAppend(ObjectData* obj, TypedValue* val) { + assert(val->m_type != KindOfRef); + auto st = static_cast(obj); + st->add(val); +} + +void c_Set::OffsetUnset(ObjectData* obj, TypedValue* key) { + c_Set::throwNoIndexAccess(); +} + +bool c_Set::Equals(ObjectData* obj1, ObjectData* obj2) { + auto st1 = static_cast(obj1); + auto st2 = static_cast(obj2); + if (st1->m_size != st2->m_size) return false; + for (uint i = 0; i <= st1->m_nLastSlot; ++i) { + c_Set::Bucket& p = st1->m_data[i]; + if (p.validValue()) { + if (p.hasInt()) { + int64_t key = p.data.m_data.num; + if (!st2->find(key)) return false; + } else { + assert(p.hasStr()); + StringData* key = p.data.m_data.pstr; + if (!st2->find(key->data(), key->size(), key->hash())) return false; + } + } + } + return true; +} + +void c_Set::Unserialize(ObjectData* obj, + VariableUnserializer* uns, + int64_t sz, + char type) { + if (type != 'V') { + throw Exception("Set does not support the '%c' serialization " + "format", type); + } + auto st = static_cast(obj); + st->reserve(sz); + for (int64_t i = 0; i < sz; ++i) { + Variant k; + // When unserializing an element of a Set, we use ColKeyMode for now. + // This will make the unserializer to reserve an id for the element + // but won't allow referencing the element via 'r' or 'R'. + k.unserialize(uns, Uns::ColKeyMode); + Bucket* p; + if (k.isInteger()) { + auto h = k.toInt64(); + p = st->findForInsert(h); + if (UNLIKELY(p->validValue())) continue; + p->setInt(h); + } else if (k.isString()) { + auto key = k.getStringData(); + auto h = key->hash(); + p = st->findForInsert(key->data(), key->size(), h); + if (UNLIKELY(p->validValue())) continue; + p->setStr(key, h); + } else { + throw Exception("Set values must be integers or strings"); + } + ++st->m_size; + ++st->m_load; + } +} + +c_SetIterator::c_SetIterator(VM::Class* cb) : + ExtObjectData(cb) { +} + +c_SetIterator::~c_SetIterator() { +} + +void c_SetIterator::t___construct() { +} + +Variant c_SetIterator::t_current() { + c_Set* st = m_obj.get(); + if (UNLIKELY(m_version != st->getVersion())) { + throw_collection_modified(); + } + if (!m_pos) { + throw_iterator_not_valid(); + } + return st->iter_value(m_pos); +} + +Variant c_SetIterator::t_key() { + return uninit_null(); +} + +bool c_SetIterator::t_valid() { + return m_pos != 0; +} + +void c_SetIterator::t_next() { + c_Set* st = m_obj.get(); + if (UNLIKELY(m_version != st->getVersion())) { + throw_collection_modified(); + } + m_pos = st->iter_next(m_pos); +} + +void c_SetIterator::t_rewind() { + c_Set* st = m_obj.get(); + if (UNLIKELY(m_version != st->getVersion())) { + throw_collection_modified(); + } + m_pos = st->iter_begin(); +} + +/////////////////////////////////////////////////////////////////////////////// + c_Pair::c_Pair(VM::Class* cb) : ExtObjectDataFlagsisCollection()); int64_t sz = collectionSize(obj); if (obj->getCollectionType() == Collection::VectorType || + obj->getCollectionType() == Collection::SetType || obj->getCollectionType() == Collection::PairType) { serializer->setObjectInfo(obj->o_getClassName(), obj->o_getId(), 'V'); serializer->writeArrayHeader(sz, true); @@ -3814,7 +4586,11 @@ void collectionSerialize(ObjectData* obj, VariableSerializer* serializer) { } } else { for (ArrayIter iter(obj); iter; ++iter) { - serializer->writeCollectionKey(iter.first()); + if (obj->getCollectionType() != Collection::SetType) { + serializer->writeCollectionKey(iter.first()); + } else { + serializer->writeCollectionKeylessPrefix(); + } serializer->writeArrayValue(iter.second()); } } @@ -3914,6 +4690,11 @@ ObjectData* collectionDeepCopyStableMap(c_StableMap* smp) { return o.detach(); } +ObjectData* collectionDeepCopySet(c_Set* st) { + Object o = st = static_cast(st->clone()); + return o.detach(); +} + ObjectData* collectionDeepCopyPair(c_Pair* pair) { Object o = pair = static_cast(pair->clone()); collectionDeepCopyTV(&pair->elm0); diff --git a/hphp/runtime/ext/ext_collections.h b/hphp/runtime/ext/ext_collections.h index bc0bb97b8..b9f29f4a8 100644 --- a/hphp/runtime/ext/ext_collections.h +++ b/hphp/runtime/ext/ext_collections.h @@ -18,8 +18,8 @@ #ifndef incl_HPHP_EXT_COLLECTION_H_ #define incl_HPHP_EXT_COLLECTION_H_ -#include -#include +#include "runtime/base/base_includes.h" +#include "system/lib/systemlib.h" namespace HPHP { /////////////////////////////////////////////////////////////////////////////// @@ -35,91 +35,83 @@ class c_Vector : public ExtObjectDataFlags { public: DECLARE_CLASS(Vector, Vector, ObjectData) - friend class c_VectorIterator; - friend class c_Map; - friend class c_StableMap; - friend class c_Pair; - friend class ArrayIter; - enum SortFlavor { IntegerSort, StringSort, GenericSort }; - - public: c_Vector(VM::Class* cls = c_Vector::s_cls); - public: ~c_Vector(); - public: void freeData(); - public: void t___construct(CVarRef iterable = null_variant); - public: Object t_add(CVarRef val); - public: Object t_addall(CVarRef val); - public: Object t_append(CVarRef val); // deprecated - public: Variant t_pop(); - public: void t_resize(CVarRef sz, CVarRef value); - public: Object t_clear(); - public: bool t_isempty(); - public: int64_t t_count(); - public: Object t_items(); - public: Object t_keys(); - public: Object t_view(); - public: Object t_kvzip(); - public: Variant t_at(CVarRef key); - public: Variant t_get(CVarRef key); - public: Object t_set(CVarRef key, CVarRef value); - public: Object t_setall(CVarRef iterable); - public: Object t_put(CVarRef key, CVarRef value); // deprecated - public: bool t_contains(CVarRef key); // deprecated - public: bool t_containskey(CVarRef key); - public: Object t_removekey(CVarRef key); - public: Array t_toarray(); - public: void t_sort(CVarRef col = uninit_null()); // deprecated - public: void t_reverse(); - public: void t_splice(CVarRef offset, CVarRef len = uninit_null(), - CVarRef replacement = uninit_null()); - public: int64_t t_linearsearch(CVarRef search_value); - public: void t_shuffle(); - public: Object t_getiterator(); - public: Object t_map(CVarRef callback); - public: Object t_filter(CVarRef callback); - public: Object t_zip(CVarRef iterable); - public: String t___tostring(); - public: Variant t___get(Variant name); - public: Variant t___set(Variant name, Variant value); - public: bool t___isset(Variant name); - public: Variant t___unset(Variant name); - public: static Object ti_fromitems(const char* cls, CVarRef iterable); - public: static Object t_fromitems(CVarRef iterable) { + public: + explicit c_Vector(VM::Class* cls = c_Vector::s_cls); + ~c_Vector(); + void freeData(); + void t___construct(CVarRef iterable = null_variant); + Object t_add(CVarRef val); + Object t_addall(CVarRef val); + Object t_append(CVarRef val); // deprecated + Variant t_pop(); + void t_resize(CVarRef sz, CVarRef value); + Object t_clear(); + bool t_isempty(); + int64_t t_count(); + Object t_items(); + Object t_keys(); + Object t_view(); + Object t_kvzip(); + Variant t_at(CVarRef key); + Variant t_get(CVarRef key); + Object t_set(CVarRef key, CVarRef value); + Object t_setall(CVarRef iterable); + Object t_put(CVarRef key, CVarRef value); // deprecated + bool t_contains(CVarRef key); // deprecated + bool t_containskey(CVarRef key); + Object t_removekey(CVarRef key); + Array t_toarray(); + void t_sort(CVarRef col = uninit_null()); // deprecated + void t_reverse(); + void t_splice(CVarRef offset, CVarRef len = uninit_null(), + CVarRef replacement = uninit_null()); + int64_t t_linearsearch(CVarRef search_value); + void t_shuffle(); + Object t_getiterator(); + Object t_map(CVarRef callback); + Object t_filter(CVarRef callback); + Object t_zip(CVarRef iterable); + String t___tostring(); + Variant t___get(Variant name); + Variant t___set(Variant name, Variant value); + bool t___isset(Variant name); + Variant t___unset(Variant name); + static Object ti_fromitems(const char* cls, CVarRef iterable); + static Object t_fromitems(CVarRef iterable) { return ti_fromitems("vector", iterable); } - public: static Object ti_fromarray(const char* cls, - CVarRef arr); // deprecated - public: static Object t_fromarray(CVarRef arr) { + static Object ti_fromarray(const char* cls, CVarRef arr); // deprecated + static Object t_fromarray(CVarRef arr) { return ti_fromarray("vector", arr); } - public: static Object ti_fromvector(const char* cls, - CVarRef vec); // deprecated - public: static Object t_fromvector(CVarRef vec) { + static Object ti_fromvector(const char* cls, CVarRef vec); // deprecated + static Object t_fromvector(CVarRef vec) { return ti_fromvector("vector", vec); } - public: static Variant ti_slice(const char* cls, CVarRef vec, CVarRef offset, - CVarRef len = uninit_null()); - public: static Variant t_slice(CVarRef vec, CVarRef offset, - CVarRef len = uninit_null()) { + static Variant ti_slice(const char* cls, CVarRef vec, CVarRef offset, + CVarRef len = uninit_null()); + static Variant t_slice(CVarRef vec, CVarRef offset, + CVarRef len = uninit_null()) { return ti_slice("vector", vec, offset, len); } - public: static void throwOOB(int64_t key) ATTRIBUTE_COLD; + static void throwOOB(int64_t key) ATTRIBUTE_COLD ATTRIBUTE_NORETURN; - public: TypedValue* at(int64_t key) { + TypedValue* at(int64_t key) { if (UNLIKELY((uint64_t)key >= (uint64_t)m_size)) { throwOOB(key); return NULL; } return &m_data[key]; } - public: TypedValue* get(int64_t key) { + TypedValue* get(int64_t key) { if ((uint64_t)key >= (uint64_t)m_size) { return NULL; } return &m_data[key]; } - public: void set(int64_t key, TypedValue* val) { + void set(int64_t key, TypedValue* val) { assert(val->m_type != KindOfRef); if (UNLIKELY((uint64_t)key >= (uint64_t)m_size)) { throwOOB(key); @@ -131,7 +123,7 @@ class c_Vector : public ExtObjectDataFlagsm_data.num = val->m_data.num; tv->m_type = val->m_type; } - public: void add(TypedValue* val) { + void add(TypedValue* val) { assert(val->m_type != KindOfRef); ++m_version; if (m_capacity <= m_size) { @@ -143,50 +135,55 @@ class c_Vector : public ExtObjectDataFlagsm_type = val->m_type; ++m_size; } - public: void resize(int64_t sz, TypedValue* val); - public: bool contains(int64_t key) { + void resize(int64_t sz, TypedValue* val); + bool contains(int64_t key) { return ((uint64_t)key < (uint64_t)m_size); } - public: void reserve(int64_t sz); - public: int getVersion() { + void reserve(int64_t sz); + int getVersion() { return m_version; } - public: Array toArrayImpl() const; + Array toArrayImpl() const; - public: Array o_toArray() const; - public: ObjectData* clone(); + Array o_toArray() const; + ObjectData* clone(); - private: + enum SortFlavor { IntegerSort, StringSort, GenericSort }; + + private: template SortFlavor preSort(const AccessorT& acc); - public: void sort(int sort_flags, bool ascending); - public: void usort(CVarRef cmp_function); + public: + void sort(int sort_flags, bool ascending); + void usort(CVarRef cmp_function); - public: static TypedValue* OffsetGet(ObjectData* obj, TypedValue* key); - public: static void OffsetSet(ObjectData* obj, TypedValue* key, - TypedValue* val); - public: static bool OffsetIsset(ObjectData* obj, TypedValue* key); - public: static bool OffsetEmpty(ObjectData* obj, TypedValue* key); - public: static bool OffsetContains(ObjectData* obj, TypedValue* key); - public: static void OffsetUnset(ObjectData* obj, TypedValue* key); - public: static void OffsetAppend(ObjectData* obj, TypedValue* val); - public: static bool Equals(ObjectData* obj1, ObjectData* obj2); - public: static void Unserialize(ObjectData* obj, - VariableUnserializer* uns, - int64_t sz, - char type); + static TypedValue* OffsetGet(ObjectData* obj, TypedValue* key); + static void OffsetSet(ObjectData* obj, TypedValue* key, TypedValue* val); + static bool OffsetIsset(ObjectData* obj, TypedValue* key); + static bool OffsetEmpty(ObjectData* obj, TypedValue* key); + static bool OffsetContains(ObjectData* obj, TypedValue* key); + static void OffsetUnset(ObjectData* obj, TypedValue* key); + static void OffsetAppend(ObjectData* obj, TypedValue* val); + static bool Equals(ObjectData* obj1, ObjectData* obj2); + static void Unserialize(ObjectData* obj, VariableUnserializer* uns, + int64_t sz, char type); private: void grow(); - static void throwBadKeyType(); + static void throwBadKeyType() ATTRIBUTE_COLD ATTRIBUTE_NORETURN; TypedValue* m_data; uint m_size; uint m_capacity; int32_t m_version; + friend class c_VectorIterator; + friend class c_Map; + friend class c_StableMap; + friend class c_Pair; + friend class ArrayIter; friend ObjectData* collectionDeepCopyVector(c_Vector* vec); }; @@ -197,23 +194,23 @@ FORWARD_DECLARE_CLASS_BUILTIN(VectorIterator); class c_VectorIterator : public ExtObjectData { public: DECLARE_CLASS(VectorIterator, VectorIterator, ObjectData) - friend class c_Vector; - - // need to implement - public: c_VectorIterator(VM::Class* cls = c_VectorIterator::s_cls); - public: ~c_VectorIterator(); - public: void t___construct(); - public: Variant t_current(); - public: Variant t_key(); - public: bool t_valid(); - public: void t_next(); - public: void t_rewind(); + public: + explicit c_VectorIterator(VM::Class* cls = c_VectorIterator::s_cls); + ~c_VectorIterator(); + void t___construct(); + Variant t_current(); + Variant t_key(); + bool t_valid(); + void t_next(); + void t_rewind(); private: SmartPtr m_obj; ssize_t m_pos; int32_t m_version; + + friend class c_Vector; }; /////////////////////////////////////////////////////////////////////////////// @@ -227,146 +224,136 @@ class c_Map : public ExtObjectDataFlags { public: DECLARE_CLASS(Map, Map, ObjectData) - friend class c_MapIterator; - friend class c_Vector; - friend class c_StableMap; - friend class ArrayIter; - public: static const int32_t KindOfTombstone = -1; - - public: c_Map(VM::Class* cls = c_Map::s_cls); - public: ~c_Map(); - public: void freeData(); - public: void t___construct(CVarRef iterable = null_variant); - public: Object t_add(CVarRef val); - public: Object t_addall(CVarRef val); - public: Object t_clear(); - public: bool t_isempty(); - public: int64_t t_count(); - public: Object t_items(); - public: Object t_keys(); - public: Object t_view(); - public: Object t_kvzip(); - public: Variant t_at(CVarRef key); - public: Variant t_get(CVarRef key); - public: Object t_set(CVarRef key, CVarRef value); - public: Object t_setall(CVarRef iterable); - public: Object t_put(CVarRef key, CVarRef value); // deprecated - public: bool t_contains(CVarRef key); - public: bool t_containskey(CVarRef key); - public: Object t_remove(CVarRef key); - public: Object t_removekey(CVarRef key); - public: Object t_discard(CVarRef key); // deprecated - public: Array t_toarray(); - public: Array t_copyasarray(); // deprecated - public: Array t_tokeysarray(); // deprecated - public: Object t_values(); // deprecated - public: Array t_tovaluesarray(); // deprecated - public: Object t_updatefromarray(CVarRef arr); - public: Object t_updatefromiterable(CVarRef it); - public: Object t_differencebykey(CVarRef it); - public: Object t_getiterator(); - public: Object t_map(CVarRef callback); - public: Object t_filter(CVarRef callback); - public: Object t_zip(CVarRef iterable); - public: String t___tostring(); - public: Variant t___get(Variant name); - public: Variant t___set(Variant name, Variant value); - public: bool t___isset(Variant name); - public: Variant t___unset(Variant name); - public: static Object ti_fromitems(const char* cls, CVarRef iterable); - public: static Object t_fromitems(CVarRef iterable) { + public: + explicit c_Map(VM::Class* cls = c_Map::s_cls); + ~c_Map(); + void freeData(); + void t___construct(CVarRef iterable = null_variant); + Object t_add(CVarRef val); + Object t_addall(CVarRef val); + Object t_clear(); + bool t_isempty(); + int64_t t_count(); + Object t_items(); + Object t_keys(); + Object t_view(); + Object t_kvzip(); + Variant t_at(CVarRef key); + Variant t_get(CVarRef key); + Object t_set(CVarRef key, CVarRef value); + Object t_setall(CVarRef iterable); + Object t_put(CVarRef key, CVarRef value); // deprecated + bool t_contains(CVarRef key); + bool t_containskey(CVarRef key); + Object t_remove(CVarRef key); + Object t_removekey(CVarRef key); + Object t_discard(CVarRef key); // deprecated + Array t_toarray(); + Array t_copyasarray(); // deprecated + Array t_tokeysarray(); // deprecated + Object t_values(); // deprecated + Array t_tovaluesarray(); // deprecated + Object t_updatefromarray(CVarRef arr); + Object t_updatefromiterable(CVarRef it); + Object t_differencebykey(CVarRef it); + Object t_getiterator(); + Object t_map(CVarRef callback); + Object t_filter(CVarRef callback); + Object t_zip(CVarRef iterable); + String t___tostring(); + Variant t___get(Variant name); + Variant t___set(Variant name, Variant value); + bool t___isset(Variant name); + Variant t___unset(Variant name); + static Object ti_fromitems(const char* cls, CVarRef iterable); + static Object t_fromitems(CVarRef iterable) { return ti_fromitems("map", iterable); } - public: static Object ti_fromarray(const char* cls, // deprecated - CVarRef arr); - public: static Object t_fromarray(CVarRef arr) { + static Object ti_fromarray(const char* cls, CVarRef arr); // deprecated + static Object t_fromarray(CVarRef arr) { return ti_fromarray("map", arr); } - public: static Object ti_fromiterable(const char* cls, // deprecated - CVarRef vec); - public: static Object t_fromiterable(CVarRef vec) { + static Object ti_fromiterable(const char* cls, CVarRef vec); // deprecated + static Object t_fromiterable(CVarRef vec) { return ti_fromiterable("map", vec); } - public: static void throwOOB(int64_t key) ATTRIBUTE_COLD; - public: static void throwOOB(StringData* key) ATTRIBUTE_COLD; + static void throwOOB(int64_t key) ATTRIBUTE_COLD ATTRIBUTE_NORETURN; + static void throwOOB(StringData* key) ATTRIBUTE_COLD ATTRIBUTE_NORETURN; - public: TypedValue* at(int64_t key) { + TypedValue* at(int64_t key) { Bucket* p = find(key); if (LIKELY(p != NULL)) return &p->data; throwOOB(key); return NULL; } - public: TypedValue* get(int64_t key) { + TypedValue* get(int64_t key) { Bucket* p = find(key); if (p) return &p->data; return NULL; } - public: TypedValue* at(StringData* key) { + TypedValue* at(StringData* key) { Bucket* p = find(key->data(), key->size(), key->hash()); if (LIKELY(p != NULL)) return &p->data; throwOOB(key); return NULL; } - public: TypedValue* get(StringData* key) { + TypedValue* get(StringData* key) { Bucket* p = find(key->data(), key->size(), key->hash()); if (p) return &p->data; return NULL; } - public: void set(int64_t key, TypedValue* val) { + void set(int64_t key, TypedValue* val) { assert(val->m_type != KindOfRef); update(key, val); } - public: void set(StringData* key, TypedValue* val) { + void set(StringData* key, TypedValue* val) { assert(val->m_type != KindOfRef); update(key, val); } - public: void add(TypedValue* val); - public: void remove(int64_t key) { + void add(TypedValue* val); + void remove(int64_t key) { ++m_version; erase(find(key)); } - public: void remove(StringData* key) { + void remove(StringData* key) { ++m_version; erase(find(key->data(), key->size(), key->hash())); } - public: bool contains(int64_t key) { + bool contains(int64_t key) { return find(key); } - public: bool contains(StringData* key) { + bool contains(StringData* key) { return find(key->data(), key->size(), key->hash()); } - public: void reserve(int64_t sz) { + void reserve(int64_t sz) { if (int64_t(m_load) + sz - int64_t(m_size) >= computeMaxLoad()) { - growImpl(sz); + adjustCapacityImpl(sz); } } - public: int getVersion() { + int getVersion() { return m_version; } - public: Array toArrayImpl() const; + Array toArrayImpl() const; - public: Array o_toArray() const; - public: ObjectData* clone(); + Array o_toArray() const; + ObjectData* clone(); - public: static TypedValue* OffsetGet(ObjectData* obj, TypedValue* key); - public: static void OffsetSet(ObjectData* obj, TypedValue* key, - TypedValue* val); - public: static bool OffsetIsset(ObjectData* obj, TypedValue* key); - public: static bool OffsetEmpty(ObjectData* obj, TypedValue* key); - public: static bool OffsetContains(ObjectData* obj, TypedValue* key); - public: static void OffsetUnset(ObjectData* obj, TypedValue* key); - public: static void OffsetAppend(ObjectData* obj, TypedValue* val); - public: static bool Equals(ObjectData* obj1, ObjectData* obj2); - public: static void Unserialize(ObjectData* obj, - VariableUnserializer* uns, - int64_t sz, - char type); + static TypedValue* OffsetGet(ObjectData* obj, TypedValue* key); + static void OffsetSet(ObjectData* obj, TypedValue* key, TypedValue* val); + static bool OffsetIsset(ObjectData* obj, TypedValue* key); + static bool OffsetEmpty(ObjectData* obj, TypedValue* key); + static bool OffsetContains(ObjectData* obj, TypedValue* key); + static void OffsetUnset(ObjectData* obj, TypedValue* key); + static void OffsetAppend(ObjectData* obj, TypedValue* val); + static bool Equals(ObjectData* obj1, ObjectData* obj2); + static void Unserialize(ObjectData* obj, VariableUnserializer* uns, + int64_t sz, char type); -public: - class Bucket { - public: + static const int32_t KindOfTombstone = -1; + + struct Bucket { /** * Buckets are 24 bytes and we allocate Buckets continguously in memory * without any padding, so some Buckets span multiple cache lines. We @@ -415,7 +402,7 @@ public: void dump(); }; -private: + private: /** * Map uses a power of two for the table size and quadratic probing to * resolve hash collisions. @@ -440,11 +427,11 @@ private: * 2 elements). */ - Bucket* m_data; - uint m_size; - uint m_load; - uint m_nLastSlot; - int32_t m_version; + Bucket* m_data; + uint m_size; + uint m_load; + uint m_nLastSlot; + int32_t m_version; size_t numSlots() const { return m_nLastSlot + 1; @@ -484,21 +471,13 @@ private: Bucket* findForInsert(const char* k, int len, strhash_t prehash) const; Bucket* findForNewInsert(size_t h0) const; - template - bool updateImpl(int64_t h, TypedValue* data); - template - bool updateImpl(StringData* key, TypedValue* data); - bool update(int64_t h, TypedValue* data) { - return updateImpl<>(h, data); - } - bool update(StringData* key, TypedValue* data) { - return updateImpl<>(key, data); - } + bool update(int64_t h, TypedValue* data); + bool update(StringData* key, TypedValue* data); void erase(Bucket* prev); - void growImpl(int64_t sz); - void grow() { - growImpl(m_size); + void adjustCapacityImpl(int64_t sz); + void adjustCapacity() { + adjustCapacityImpl(m_size); } void deleteBuckets(); @@ -509,9 +488,13 @@ private: Variant iter_key(ssize_t pos) const; Variant iter_value(ssize_t pos) const; - static void throwBadKeyType(); + static void throwBadKeyType() ATTRIBUTE_COLD ATTRIBUTE_NORETURN; friend ObjectData* collectionDeepCopyMap(c_Map* mp); + friend class c_MapIterator; + friend class c_Vector; + friend class c_StableMap; + friend class ArrayIter; }; /////////////////////////////////////////////////////////////////////////////// @@ -521,23 +504,23 @@ FORWARD_DECLARE_CLASS_BUILTIN(MapIterator); class c_MapIterator : public ExtObjectData { public: DECLARE_CLASS(MapIterator, MapIterator, ObjectData) - friend class c_Map; - - // need to implement - public: c_MapIterator(VM::Class* cls = c_MapIterator::s_cls); - public: ~c_MapIterator(); - public: void t___construct(); - public: Variant t_current(); - public: Variant t_key(); - public: bool t_valid(); - public: void t_next(); - public: void t_rewind(); + public: + explicit c_MapIterator(VM::Class* cls = c_MapIterator::s_cls); + ~c_MapIterator(); + void t___construct(); + Variant t_current(); + Variant t_key(); + bool t_valid(); + void t_next(); + void t_rewind(); private: SmartPtr m_obj; ssize_t m_pos; int32_t m_version; + + friend class c_Map; }; /////////////////////////////////////////////////////////////////////////////// @@ -551,149 +534,137 @@ class c_StableMap : public ExtObjectDataFlags { public: DECLARE_CLASS(StableMap, StableMap, ObjectData) - friend class c_StableMapIterator; - friend class c_Vector; - friend class c_Map; - friend class ArrayIter; - enum SortFlavor { IntegerSort, StringSort, GenericSort }; - - public: c_StableMap(VM::Class* cls = c_StableMap::s_cls); - public: ~c_StableMap(); - public: void freeData(); - public: void t___construct(CVarRef iterable = null_variant); - public: Object t_add(CVarRef val); - public: Object t_addall(CVarRef val); - public: Object t_clear(); - public: bool t_isempty(); - public: int64_t t_count(); - public: Object t_items(); - public: Object t_keys(); - public: Object t_view(); - public: Object t_kvzip(); - public: Variant t_at(CVarRef key); - public: Variant t_get(CVarRef key); - public: Object t_set(CVarRef key, CVarRef value); - public: Object t_setall(CVarRef iterable); - public: Object t_put(CVarRef key, CVarRef value); // deprecated - public: bool t_contains(CVarRef key); - public: bool t_containskey(CVarRef key); - public: Object t_remove(CVarRef key); - public: Object t_removekey(CVarRef key); - public: Object t_discard(CVarRef key); // deprecated - public: Array t_toarray(); - public: Array t_copyasarray(); // deprecated - public: Array t_tokeysarray(); // deprecated - public: Object t_values(); // deprecated - public: Array t_tovaluesarray(); // deprecated - public: Object t_updatefromarray(CVarRef arr); - public: Object t_updatefromiterable(CVarRef it); - public: Object t_differencebykey(CVarRef it); - public: Object t_getiterator(); - public: Object t_map(CVarRef callback); - public: Object t_filter(CVarRef callback); - public: Object t_zip(CVarRef iterable); - public: String t___tostring(); - public: Variant t___get(Variant name); - public: Variant t___set(Variant name, Variant value); - public: bool t___isset(Variant name); - public: Variant t___unset(Variant name); - public: static Object ti_fromitems(const char* cls, CVarRef iterable); - public: static Object t_fromitems(CVarRef iterable) { + public: + explicit c_StableMap(VM::Class* cls = c_StableMap::s_cls); + ~c_StableMap(); + void freeData(); + void t___construct(CVarRef iterable = null_variant); + Object t_add(CVarRef val); + Object t_addall(CVarRef val); + Object t_clear(); + bool t_isempty(); + int64_t t_count(); + Object t_items(); + Object t_keys(); + Object t_view(); + Object t_kvzip(); + Variant t_at(CVarRef key); + Variant t_get(CVarRef key); + Object t_set(CVarRef key, CVarRef value); + Object t_setall(CVarRef iterable); + Object t_put(CVarRef key, CVarRef value); // deprecated + bool t_contains(CVarRef key); + bool t_containskey(CVarRef key); + Object t_remove(CVarRef key); + Object t_removekey(CVarRef key); + Object t_discard(CVarRef key); // deprecated + Array t_toarray(); + Array t_copyasarray(); // deprecated + Array t_tokeysarray(); // deprecated + Object t_values(); // deprecated + Array t_tovaluesarray(); // deprecated + Object t_updatefromarray(CVarRef arr); + Object t_updatefromiterable(CVarRef it); + Object t_differencebykey(CVarRef it); + Object t_getiterator(); + Object t_map(CVarRef callback); + Object t_filter(CVarRef callback); + Object t_zip(CVarRef iterable); + String t___tostring(); + Variant t___get(Variant name); + Variant t___set(Variant name, Variant value); + bool t___isset(Variant name); + Variant t___unset(Variant name); + static Object ti_fromitems(const char* cls, CVarRef iterable); + static Object t_fromitems(CVarRef iterable) { return ti_fromitems("stablemap", iterable); } - public: static Object ti_fromarray(const char* cls, - CVarRef arr); // deprecated - public: static Object t_fromarray(CVarRef arr) { + static Object ti_fromarray(const char* cls, CVarRef arr); // deprecated + static Object t_fromarray(CVarRef arr) { return ti_fromarray("stablemap", arr); } - public: static Object ti_fromiterable(const char* cls, - CVarRef vec); // deprecated - public: static Object t_fromiterable(CVarRef vec) { + static Object ti_fromiterable(const char* cls, CVarRef vec); // deprecated + static Object t_fromiterable(CVarRef vec) { return ti_fromiterable("stablemap", vec); } - public: static void throwOOB(int64_t key) ATTRIBUTE_COLD; - public: static void throwOOB(StringData* key) ATTRIBUTE_COLD; + static void throwOOB(int64_t key) ATTRIBUTE_COLD ATTRIBUTE_NORETURN; + static void throwOOB(StringData* key) ATTRIBUTE_COLD ATTRIBUTE_NORETURN; - public: TypedValue* at(int64_t key) { + TypedValue* at(int64_t key) { Bucket* p = find(key); if (LIKELY(p != NULL)) return &p->data; throwOOB(key); return NULL; } - public: TypedValue* at(StringData* key) { + TypedValue* at(StringData* key) { Bucket* p = find(key->data(), key->size(), key->hash()); if (LIKELY(p != NULL)) return &p->data; throwOOB(key); return NULL; } - public: TypedValue* get(int64_t key) { + TypedValue* get(int64_t key) { Bucket* p = find(key); if (p != NULL) return &p->data; return NULL; } - public: TypedValue* get(StringData* key) { + TypedValue* get(StringData* key) { Bucket* p = find(key->data(), key->size(), key->hash()); if (p != NULL) return &p->data; return NULL; } - public: void set(int64_t key, TypedValue* val) { + void set(int64_t key, TypedValue* val) { update(key, val); } - public: void set(StringData* key, TypedValue* val) { + void set(StringData* key, TypedValue* val) { update(key, val); } - public: void add(TypedValue* val); - public: void remove(int64_t key) { + void add(TypedValue* val); + void remove(int64_t key) { ++m_version; erase(findForErase(key)); } - public: void remove(StringData* key) { + void remove(StringData* key) { ++m_version; erase(findForErase(key->data(), key->size(), key->hash())); } - public: bool contains(int64_t key) { + bool contains(int64_t key) { return find(key); } - public: bool contains(StringData* key) { + bool contains(StringData* key) { return find(key->data(), key->size(), key->hash()); } - public: void reserve(int64_t sz) { + void reserve(int64_t sz) { if (sz > int64_t(m_nTableSize)) { - growImpl(sz); + adjustCapacityImpl(sz); } } - public: int getVersion() { + int getVersion() { return m_version; } - public: Array toArrayImpl() const; + Array toArrayImpl() const; - public: Array o_toArray() const; - public: ObjectData* clone(); + Array o_toArray() const; + ObjectData* clone(); - public: static TypedValue* OffsetGet(ObjectData* obj, TypedValue* key); - public: static void OffsetSet(ObjectData* obj, TypedValue* key, - TypedValue* val); - public: static bool OffsetIsset(ObjectData* obj, TypedValue* key); - public: static bool OffsetEmpty(ObjectData* obj, TypedValue* key); - public: static bool OffsetContains(ObjectData* obj, TypedValue* key); - public: static void OffsetUnset(ObjectData* obj, TypedValue* key); - public: static void OffsetAppend(ObjectData* obj, TypedValue* val); - public: static bool Equals(ObjectData* obj1, ObjectData* obj2); - public: static void Unserialize(ObjectData* obj, - VariableUnserializer* uns, - int64_t sz, - char type); + static TypedValue* OffsetGet(ObjectData* obj, TypedValue* key); + static void OffsetSet(ObjectData* obj, TypedValue* key, TypedValue* val); + static bool OffsetIsset(ObjectData* obj, TypedValue* key); + static bool OffsetEmpty(ObjectData* obj, TypedValue* key); + static bool OffsetContains(ObjectData* obj, TypedValue* key); + static void OffsetUnset(ObjectData* obj, TypedValue* key); + static void OffsetAppend(ObjectData* obj, TypedValue* val); + static bool Equals(ObjectData* obj1, ObjectData* obj2); + static void Unserialize(ObjectData* obj, VariableUnserializer* uns, + int64_t sz, char type); -public: - class Bucket { - public: + struct Bucket { Bucket() : ikey(0), pListNext(nullptr), pListLast(nullptr), pNext(nullptr) { data.hash() = 0; } - Bucket(TypedValue* tv) : ikey(0), pListNext(nullptr), pListLast(nullptr), - pNext(nullptr) { + explicit Bucket(TypedValue* tv) : ikey(0), pListNext(nullptr), + pListLast(nullptr), pNext(nullptr) { tvDup(tv, &data); data.hash() = 0; } @@ -743,46 +714,41 @@ public: void dump(); }; - private: + enum SortFlavor { IntegerSort, StringSort, GenericSort }; + + private: template SortFlavor preSort(Bucket** buffer, const AccessorT& acc, bool checkTypes); - private: void postSort(Bucket** buffer); + void postSort(Bucket** buffer); - public: void asort(int sort_flags, bool ascending); - public: void ksort(int sort_flags, bool ascending); - public: void uasort(CVarRef cmp_function); - public: void uksort(CVarRef cmp_function); + public: + void asort(int sort_flags, bool ascending); + void ksort(int sort_flags, bool ascending); + void uasort(CVarRef cmp_function); + void uksort(CVarRef cmp_function); -private: - uint m_size; - uint m_nTableSize; - uint m_nTableMask; - int32_t m_version; - Bucket* m_pListHead; - Bucket* m_pListTail; - Bucket** m_arBuckets; + private: + uint m_size; + uint m_nTableSize; + uint m_nTableMask; + int32_t m_version; + Bucket* m_pListHead; + Bucket* m_pListTail; + Bucket** m_arBuckets; Bucket* find(int64_t h) const; Bucket* find(const char* k, int len, strhash_t prehash) const; Bucket** findForErase(int64_t h) const; Bucket** findForErase(const char* k, int len, strhash_t prehash) const; - template - bool updateImpl(int64_t h, TypedValue* data); - template - bool updateImpl(StringData* key, TypedValue* data); - bool update(int64_t h, TypedValue* data) { - return updateImpl<>(h, data); - } - bool update(StringData* key, TypedValue* data) { - return updateImpl<>(key, data); - } + bool update(int64_t h, TypedValue* data); + bool update(StringData* key, TypedValue* data); void erase(Bucket** prev); - void growImpl(int64_t sz); - void grow() { - growImpl(m_size); + void adjustCapacityImpl(int64_t sz); + void adjustCapacity() { + adjustCapacityImpl(m_size); } void deleteBuckets(); @@ -793,9 +759,13 @@ private: Variant iter_key(ssize_t pos) const; Variant iter_value(ssize_t pos) const; - static void throwBadKeyType(); + static void throwBadKeyType() ATTRIBUTE_COLD ATTRIBUTE_NORETURN; friend ObjectData* collectionDeepCopyStableMap(c_StableMap* smp); + friend class c_StableMapIterator; + friend class c_Vector; + friend class c_Map; + friend class ArrayIter; }; /////////////////////////////////////////////////////////////////////////////// @@ -805,23 +775,258 @@ FORWARD_DECLARE_CLASS_BUILTIN(StableMapIterator); class c_StableMapIterator : public ExtObjectData { public: DECLARE_CLASS(StableMapIterator, StableMapIterator, ObjectData) - friend class c_StableMap; - - // need to implement - public: c_StableMapIterator(VM::Class* cls = c_StableMapIterator::s_cls); - public: ~c_StableMapIterator(); - public: void t___construct(); - public: Variant t_current(); - public: Variant t_key(); - public: bool t_valid(); - public: void t_next(); - public: void t_rewind(); + public: + explicit c_StableMapIterator(VM::Class* cls = c_StableMapIterator::s_cls); + ~c_StableMapIterator(); + void t___construct(); + Variant t_current(); + Variant t_key(); + bool t_valid(); + void t_next(); + void t_rewind(); private: SmartPtr m_obj; ssize_t m_pos; int32_t m_version; + + friend class c_StableMap; +}; + +/////////////////////////////////////////////////////////////////////////////// +// class Set + +FORWARD_DECLARE_CLASS_BUILTIN(Set); +class c_Set : public ExtObjectDataFlags { + public: + DECLARE_CLASS(Set, Set, ObjectData) + + public: + static const int32_t KindOfTombstone = -1; + + explicit c_Set(VM::Class* cls = c_Set::s_cls); + ~c_Set(); + void freeData(); + void t___construct(CVarRef iterable = null_variant); + Object t_add(CVarRef val); + Object t_addall(CVarRef val); + Object t_clear(); + bool t_isempty(); + int64_t t_count(); + Object t_items(); + Object t_view(); + bool t_contains(CVarRef key); + Object t_remove(CVarRef key); + Object t_discard(CVarRef key); + Array t_toarray(); + Object t_getiterator(); + Object t_map(CVarRef callback); + Object t_filter(CVarRef callback); + Object t_zip(CVarRef iterable); + Object t_difference(CVarRef iterable); + Object t_updatefromarrayvalues(CVarRef arr); + Object t_updatefromiterablevalues(CVarRef iterable); + String t___tostring(); + Variant t___get(Variant name); + Variant t___set(Variant name, Variant value); + bool t___isset(Variant name); + Variant t___unset(Variant name); + static Object ti_fromitems(const char* cls, CVarRef iterable); + static Object t_fromitems(CVarRef iterable) { + return ti_fromitems("set", iterable); + } + static Object ti_fromarray(const char* cls, CVarRef arr); + static Object t_fromarray(CVarRef arr) { + return ti_fromarray("set", arr); + } + static Object ti_fromarrays(const char* cls, int _argc, + CArrRef _argv = null_array); + static Object t_fromarrays(int _argc, CArrRef _argv = null_array) { + return ti_fromarrays("set", _argc, _argv); + } + static Object ti_fromiterablevalues(const char* cls, CVarRef iterable); + static Object t_fromiterablevalues(CVarRef iterable) { + return ti_fromiterablevalues("set", iterable); + } + + static void throwOOB(int64_t key) ATTRIBUTE_COLD ATTRIBUTE_NORETURN; + static void throwOOB(StringData* key) ATTRIBUTE_COLD ATTRIBUTE_NORETURN; + static void throwNoIndexAccess() ATTRIBUTE_COLD ATTRIBUTE_NORETURN; + + void add(TypedValue* val); + void remove(int64_t key) { + ++m_version; + erase(find(key)); + } + void remove(StringData* key) { + ++m_version; + erase(find(key->data(), key->size(), key->hash())); + } + bool contains(int64_t key) { + return find(key); + } + bool contains(StringData* key) { + return find(key->data(), key->size(), key->hash()); + } + void reserve(int64_t sz) { + if (int64_t(m_load) + sz - int64_t(m_size) >= computeMaxLoad()) { + adjustCapacityImpl(sz); + } + } + int getVersion() { + return m_version; + } + Array toArrayImpl() const; + + Array o_toArray() const; + ObjectData* clone(); + + static TypedValue* OffsetGet(ObjectData* obj, TypedValue* key); + static void OffsetSet(ObjectData* obj, TypedValue* key, TypedValue* val); + static bool OffsetIsset(ObjectData* obj, TypedValue* key); + static bool OffsetEmpty(ObjectData* obj, TypedValue* key); + static bool OffsetContains(ObjectData* obj, TypedValue* key); + static void OffsetUnset(ObjectData* obj, TypedValue* key); + static void OffsetAppend(ObjectData* obj, TypedValue* val); + static bool Equals(ObjectData* obj1, ObjectData* obj2); + static void Unserialize(ObjectData* obj, VariableUnserializer* uns, + int64_t sz, char type); + + struct Bucket { + /** + * Buckets are 16 bytes. We use m_aux for our own nefarious purposes. + * It is critical that when we return &data to clients, that they not + * read or write the m_aux field. + */ + TypedValueAux data; + inline bool hasStr() const { return IS_STRING_TYPE(data.m_type); } + inline bool hasInt() const { return data.m_type == KindOfInt64; } + inline void setStr(StringData* k, strhash_t h) { + k->incRefCount(); + data.m_data.pstr = k; + data.m_type = KindOfString; + data.hash() = int32_t(h) | 0x80000000; + } + inline void setInt(int64_t k) { + data.m_data.num = k; + data.m_type = KindOfInt64; + data.hash() = int32_t(k) | 0x80000000; + } + inline int32_t hash() const { + return data.hash(); + } + bool validValue() const { + return (intptr_t(data.m_type) > 0); + } + bool empty() const { + return data.m_type == KindOfUninit; + } + bool tombstone() const { + return data.m_type == KindOfTombstone; + } + void dump(); + }; + + private: + /** + * Set uses a power of two for the table size and quadratic probing to + * resolve hash collisions, similar to the Map class. See the comments + * in the Map class for more details on how the hashtable works and how + * we decide when to grow or shrink the table. + */ + + Bucket* m_data; + uint m_size; + uint m_load; + uint m_nLastSlot; + int32_t m_version; + + size_t numSlots() const { + return m_nLastSlot + 1; + } + + // The maximum load factor is 75%. + size_t computeMaxLoad() const { + size_t n = numSlots(); + return (n - (n >> 2)); + } + + // When the map is not empty, the minimum allowed ratio + // of # elements / # slots is 18.75%. + size_t computeMinElements() const { + size_t n = numSlots(); + return ((n >> 3) + ((n+8) >> 4)); + } + + Bucket* fetchBucket(Bucket* data, intptr_t slot) const { + return &data[slot]; + } + + Bucket* fetchBucket(intptr_t slot) const { + return fetchBucket(m_data, slot); + } + + Bucket* find(int64_t h) const; + Bucket* find(const char* k, int len, strhash_t prehash) const; + Bucket* findForInsert(int64_t h) const; + Bucket* findForInsert(const char* k, int len, strhash_t prehash) const; + Bucket* findForNewInsert(size_t h0) const; + + void update(int64_t h); + void update(StringData* key); + void erase(Bucket* prev); + + void adjustCapacityImpl(int64_t sz); + void adjustCapacity() { + adjustCapacityImpl(m_size); + } + + void deleteBuckets(); + + ssize_t iter_begin() const; + ssize_t iter_next(ssize_t prev) const; + ssize_t iter_prev(ssize_t prev) const; + Variant iter_value(ssize_t pos) const; + + static void throwBadValueType() ATTRIBUTE_COLD ATTRIBUTE_NORETURN; + + friend ObjectData* collectionDeepCopySet(c_Set* st); + friend class c_SetIterator; + friend class c_Vector; + friend class c_Map; + friend class c_StableMap; + friend class ArrayIter; +}; + +/////////////////////////////////////////////////////////////////////////////// +// class SetIterator + +FORWARD_DECLARE_CLASS_BUILTIN(SetIterator); +class c_SetIterator : public ExtObjectData { + public: + DECLARE_CLASS(SetIterator, SetIterator, ObjectData) + + public: + explicit c_SetIterator(VM::Class* cls = c_SetIterator::s_cls); + ~c_SetIterator(); + void t___construct(); + Variant t_current(); + Variant t_key(); + bool t_valid(); + void t_next(); + void t_rewind(); + + private: + SmartPtr m_obj; + ssize_t m_pos; + int32_t m_version; + + friend class c_Set; }; /////////////////////////////////////////////////////////////////////////////// @@ -836,51 +1041,46 @@ class c_Pair : public ExtObjectDataFlags= uint64_t(2))) { throwOOB(key); return NULL; } return &getElms()[key]; } - public: TypedValue* get(int64_t key) { + TypedValue* get(int64_t key) { if (uint64_t(key) >= uint64_t(2)) { return NULL; } return &getElms()[key]; } - public: void add(TypedValue* val) { + void add(TypedValue* val) { assert(val->m_type != KindOfRef); if (m_size == 2) { Object e(SystemLib::AllocRuntimeExceptionObject( @@ -893,7 +1093,7 @@ class c_Pair : public ExtObjectDataFlagsm_type = val->m_type; ++m_size; } - public: void addInt(int64_t val) { + void addInt(int64_t val) { if (m_size == 2) { Object e(SystemLib::AllocRuntimeExceptionObject( "Cannot add a new element to a Pair")); @@ -904,33 +1104,32 @@ class c_Pair : public ExtObjectDataFlagsm_type = KindOfInt64; ++m_size; } - public: bool contains(int64_t key) { + bool contains(int64_t key) { return (uint64_t(key) < uint64_t(2)); } - public: Array toArrayImpl() const; + Array toArrayImpl() const; - public: Array o_toArray() const; - public: ObjectData* clone(); + Array o_toArray() const; + ObjectData* clone(); - public: static TypedValue* OffsetGet(ObjectData* obj, TypedValue* key); - public: static void OffsetSet(ObjectData* obj, TypedValue* key, - TypedValue* val); - public: static bool OffsetIsset(ObjectData* obj, TypedValue* key); - public: static bool OffsetEmpty(ObjectData* obj, TypedValue* key); - public: static bool OffsetContains(ObjectData* obj, TypedValue* key); - public: static void OffsetUnset(ObjectData* obj, TypedValue* key); - public: static void OffsetAppend(ObjectData* obj, TypedValue* val); - public: static bool Equals(ObjectData* obj1, ObjectData* obj2); - public: static void Unserialize(ObjectData* obj, - VariableUnserializer* uns, - int64_t sz, - char type); + static TypedValue* OffsetGet(ObjectData* obj, TypedValue* key); + static void OffsetSet(ObjectData* obj, TypedValue* key, TypedValue* val); + static bool OffsetIsset(ObjectData* obj, TypedValue* key); + static bool OffsetEmpty(ObjectData* obj, TypedValue* key); + static bool OffsetContains(ObjectData* obj, TypedValue* key); + static void OffsetUnset(ObjectData* obj, TypedValue* key); + static void OffsetAppend(ObjectData* obj, TypedValue* val); + static bool Equals(ObjectData* obj1, ObjectData* obj2); + static void Unserialize(ObjectData* obj, VariableUnserializer* uns, + int64_t sz, char type); private: - static void throwBadKeyType(); + static void throwBadKeyType() ATTRIBUTE_COLD ATTRIBUTE_NORETURN; uint m_size; + + // TODO Can we add something here to make sure elm0 is 16-byte aligned? TypedValue elm0; TypedValue elm1; @@ -939,7 +1138,12 @@ class c_Pair : public ExtObjectDataFlags m_obj; ssize_t m_pos; + + friend class c_Pair; }; /////////////////////////////////////////////////////////////////////////////// @@ -980,6 +1185,8 @@ inline TypedValue* collectionGet(ObjectData* obj, TypedValue* key) { return c_Map::OffsetGet(obj, key); case Collection::StableMapType: return c_StableMap::OffsetGet(obj, key); + case Collection::SetType: + return c_Set::OffsetGet(obj, key); case Collection::PairType: return c_Pair::OffsetGet(obj, key); default: @@ -1002,6 +1209,9 @@ inline void collectionSet(ObjectData* obj, TypedValue* key, TypedValue* val) { case Collection::StableMapType: c_StableMap::OffsetSet(obj, key, val); break; + case Collection::SetType: + c_Set::OffsetSet(obj, key, val); + break; case Collection::PairType: c_Pair::OffsetSet(obj, key, val); break; @@ -1019,6 +1229,8 @@ inline bool collectionIsset(ObjectData* obj, TypedValue* key) { return c_Map::OffsetIsset(obj, key); case Collection::StableMapType: return c_StableMap::OffsetIsset(obj, key); + case Collection::SetType: + return c_Set::OffsetIsset(obj, key); case Collection::PairType: return c_Pair::OffsetIsset(obj, key); default: @@ -1036,6 +1248,8 @@ inline bool collectionEmpty(ObjectData* obj, TypedValue* key) { return c_Map::OffsetEmpty(obj, key); case Collection::StableMapType: return c_StableMap::OffsetEmpty(obj, key); + case Collection::SetType: + return c_Set::OffsetEmpty(obj, key); case Collection::PairType: return c_Pair::OffsetEmpty(obj, key); default: @@ -1056,6 +1270,9 @@ inline void collectionUnset(ObjectData* obj, TypedValue* key) { case Collection::StableMapType: c_StableMap::OffsetUnset(obj, key); break; + case Collection::SetType: + c_Set::OffsetUnset(obj, key); + break; case Collection::PairType: c_Pair::OffsetUnset(obj, key); break; @@ -1077,6 +1294,9 @@ inline void collectionAppend(ObjectData* obj, TypedValue* val) { case Collection::StableMapType: c_StableMap::OffsetAppend(obj, val); break; + case Collection::SetType: + c_Set::OffsetAppend(obj, val); + break; case Collection::PairType: c_Pair::OffsetAppend(obj, val); break; @@ -1099,6 +1319,9 @@ inline Variant& collectionOffsetGet(ObjectData* obj, int64_t offset) { c_StableMap* smp = static_cast(obj); return tvAsVariant(smp->at(offset)); } + case Collection::SetType: { + c_Set::throwNoIndexAccess(); + } case Collection::PairType: { c_Pair* pair = static_cast(obj); return tvAsVariant(pair->at(offset)); @@ -1125,6 +1348,9 @@ inline Variant& collectionOffsetGet(ObjectData* obj, CStrRef offset) { c_StableMap* smp = static_cast(obj); return tvAsVariant(smp->at(key)); } + case Collection::SetType: { + c_Set::throwNoIndexAccess(); + } case Collection::PairType: { Object e(SystemLib::AllocInvalidArgumentExceptionObject( "Only integer keys may be used with Pairs")); @@ -1145,6 +1371,8 @@ inline Variant& collectionOffsetGet(ObjectData* obj, CVarRef offset) { return tvAsVariant(c_Map::OffsetGet(obj, key)); case Collection::StableMapType: return tvAsVariant(c_StableMap::OffsetGet(obj, key)); + case Collection::SetType: + return tvAsVariant(c_Set::OffsetGet(obj, key)); case Collection::PairType: return tvAsVariant(c_Pair::OffsetGet(obj, key)); default: @@ -1186,6 +1414,9 @@ inline void collectionOffsetSet(ObjectData* obj, int64_t offset, CVarRef val) { smp->set(offset, tv); break; } + case Collection::SetType: { + c_Set::throwNoIndexAccess(); + } case Collection::PairType: { Object e(SystemLib::AllocRuntimeExceptionObject( "Cannot assign to an element of a Pair")); @@ -1218,6 +1449,9 @@ inline void collectionOffsetSet(ObjectData* obj, CStrRef offset, CVarRef val) { smp->set(key, tv); break; } + case Collection::SetType: { + c_Set::throwNoIndexAccess(); + } case Collection::PairType: { Object e(SystemLib::AllocRuntimeExceptionObject( "Cannot assign to an element of a Pair")); @@ -1247,6 +1481,10 @@ inline void collectionOffsetSet(ObjectData* obj, CVarRef offset, CVarRef val) { c_StableMap::OffsetSet(obj, key, tv); break; } + case Collection::SetType: { + c_Set::OffsetSet(obj, key, tv); + break; + } case Collection::PairType: { c_Pair::OffsetSet(obj, key, tv); break; @@ -1277,6 +1515,8 @@ inline bool collectionOffsetContains(ObjectData* obj, CVarRef offset) { return c_Map::OffsetContains(obj, key); case Collection::StableMapType: return c_StableMap::OffsetContains(obj, key); + case Collection::SetType: + return c_Set::OffsetContains(obj, key); case Collection::PairType: return c_Pair::OffsetContains(obj, key); default: @@ -1294,6 +1534,8 @@ inline bool collectionOffsetIsset(ObjectData* obj, CVarRef offset) { return c_Map::OffsetIsset(obj, key); case Collection::StableMapType: return c_StableMap::OffsetIsset(obj, key); + case Collection::SetType: + return c_Set::OffsetIsset(obj, key); case Collection::PairType: return c_Pair::OffsetIsset(obj, key); default: @@ -1311,6 +1553,8 @@ inline bool collectionOffsetEmpty(ObjectData* obj, CVarRef offset) { return c_Map::OffsetEmpty(obj, key); case Collection::StableMapType: return c_StableMap::OffsetEmpty(obj, key); + case Collection::SetType: + return c_Set::OffsetEmpty(obj, key); case Collection::PairType: return c_Pair::OffsetEmpty(obj, key); default: @@ -1337,6 +1581,8 @@ inline int64_t collectionSize(ObjectData* obj) { return static_cast(obj)->t_count(); case Collection::StableMapType: return static_cast(obj)->t_count(); + case Collection::SetType: + return static_cast(obj)->t_count(); case Collection::PairType: return static_cast(obj)->t_count(); default: @@ -1356,6 +1602,9 @@ inline void collectionReserve(ObjectData* obj, int64_t sz) { case Collection::StableMapType: static_cast(obj)->reserve(sz); break; + case Collection::SetType: + static_cast(obj)->reserve(sz); + break; case Collection::PairType: // do nothing break; @@ -1375,15 +1624,18 @@ inline void collectionUnserialize(ObjectData* obj, case Collection::VectorType: c_Vector::Unserialize(obj, uns, sz, type); break; - case Collection::PairType: - c_Pair::Unserialize(obj, uns, sz, type); - break; case Collection::MapType: c_Map::Unserialize(obj, uns, sz, type); break; case Collection::StableMapType: c_StableMap::Unserialize(obj, uns, sz, type); break; + case Collection::SetType: + c_Set::Unserialize(obj, uns, sz, type); + break; + case Collection::PairType: + c_Pair::Unserialize(obj, uns, sz, type); + break; default: assert(false); } @@ -1399,6 +1651,8 @@ inline bool collectionEquals(ObjectData* obj1, ObjectData* obj2) { return c_Map::Equals(obj1, obj2); case Collection::StableMapType: return c_StableMap::Equals(obj1, obj2); + case Collection::SetType: + return c_Set::Equals(obj1, obj2); case Collection::PairType: return c_Pair::Equals(obj1, obj2); default: @@ -1412,6 +1666,7 @@ ArrayData* collectionDeepCopyArray(ArrayData* arr); ObjectData* collectionDeepCopyVector(c_Vector* vec); ObjectData* collectionDeepCopyMap(c_Map* mp); ObjectData* collectionDeepCopyStableMap(c_StableMap* smp); +ObjectData* collectionDeepCopySet(c_Set* st); ObjectData* collectionDeepCopyPair(c_Pair* pair); /////////////////////////////////////////////////////////////////////////////// diff --git a/hphp/runtime/vm/bytecode.cpp b/hphp/runtime/vm/bytecode.cpp index 1fb7de5e5..57b5f4584 100644 --- a/hphp/runtime/vm/bytecode.cpp +++ b/hphp/runtime/vm/bytecode.cpp @@ -3785,6 +3785,7 @@ inline void OPTBLD_INLINE VMExecutionContext::iopNewCol(PC& pc) { case Collection::VectorType: obj = NEWOBJ(c_Vector)(); break; case Collection::MapType: obj = NEWOBJ(c_Map)(); break; case Collection::StableMapType: obj = NEWOBJ(c_StableMap)(); break; + case Collection::SetType: obj = NEWOBJ(c_Set)(); break; case Collection::PairType: obj = NEWOBJ(c_Pair)(); break; default: obj = nullptr; diff --git a/hphp/runtime/vm/runtime.cpp b/hphp/runtime/vm/runtime.cpp index 46564083d..4e33d79ed 100644 --- a/hphp/runtime/vm/runtime.cpp +++ b/hphp/runtime/vm/runtime.cpp @@ -97,6 +97,7 @@ ArrayData* new_tuple(int n, const TypedValue* values) { NEW_COLLECTION_HELPER(Vector) NEW_COLLECTION_HELPER(Map) NEW_COLLECTION_HELPER(StableMap) +NEW_COLLECTION_HELPER(Set) ObjectData* newPairHelper() { ObjectData *obj = NEWOBJ(c_Pair)(); @@ -428,6 +429,11 @@ void collection_setm_ik1_v0(ObjectData* obj, int64_t key, TypedValue* value) { smp->set(key, value); break; } + case Collection::SetType: { + Object e(SystemLib::AllocRuntimeExceptionObject( + "Set does not support $c[$k] syntax")); + throw e; + } case Collection::PairType: { Object e(SystemLib::AllocRuntimeExceptionObject( "Cannot assign to an element of a Pair")); @@ -457,6 +463,11 @@ void collection_setm_sk1_v0(ObjectData* obj, StringData* key, smp->set(key, value); break; } + case Collection::SetType: { + Object e(SystemLib::AllocRuntimeExceptionObject( + "Set does not support $c[$k] syntax")); + throw e; + } case Collection::PairType: { Object e(SystemLib::AllocRuntimeExceptionObject( "Cannot assign to an element of a Pair")); diff --git a/hphp/runtime/vm/runtime.h b/hphp/runtime/vm/runtime.h index e104df4c2..99832b1c3 100644 --- a/hphp/runtime/vm/runtime.h +++ b/hphp/runtime/vm/runtime.h @@ -33,6 +33,7 @@ ArrayData* new_tuple(int numArgs, const TypedValue* args); ObjectData* newVectorHelper(int nElms); ObjectData* newMapHelper(int nElms); ObjectData* newStableMapHelper(int nElms); +ObjectData* newSetHelper(int nElms); ObjectData* newPairHelper(); StringData* concat_is(int64_t v1, StringData* v2); diff --git a/hphp/runtime/vm/translator/translator-x64.cpp b/hphp/runtime/vm/translator/translator-x64.cpp index bab9c81c7..8515b9e95 100644 --- a/hphp/runtime/vm/translator/translator-x64.cpp +++ b/hphp/runtime/vm/translator/translator-x64.cpp @@ -5274,6 +5274,7 @@ TranslatorX64::translateNewCol(const Tracelet& t, case Collection::VectorType: fptr = (void*)newVectorHelper; break; case Collection::MapType: fptr = (void*)newMapHelper; break; case Collection::StableMapType: fptr = (void*)newStableMapHelper; break; + case Collection::SetType: fptr = (void*)newSetHelper; break; case Collection::PairType: fptr = (void*)newPairHelper; break; default: assert(false); break; } @@ -5281,7 +5282,8 @@ TranslatorX64::translateNewCol(const Tracelet& t, ObjectData* obj1 UNUSED = newVectorHelper(42); ObjectData* obj2 UNUSED = newMapHelper(42); ObjectData* obj3 UNUSED = newStableMapHelper(42); - ObjectData* obj4 UNUSED = newPairHelper(); + ObjectData* obj4 UNUSED = newSetHelper(42); + ObjectData* obj5 UNUSED = newPairHelper(); } if (cType == Collection::PairType) { // newPairHelper does not take any arguments, since Pairs always diff --git a/hphp/system/class_map.cpp b/hphp/system/class_map.cpp index e7e8f7e75..ff7cb34f1 100644 --- a/hphp/system/class_map.cpp +++ b/hphp/system/class_map.cpp @@ -20749,6 +20749,210 @@ const char *g_class_map[] = { NULL, NULL, NULL, + (const char *)0x10006020, "Set", "", "", (const char *)0, (const char *)0, + "/**\n * ( excerpt from http://php.net/manual/en/class.set.php )\n *\n * An unordered set-style collection.\n *\n */", + "mutableset", NULL, + (const char *)0x10006040, "__construct", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.construct.php )\n *\n * Returns a Set built from the values produced by the specified Iterable.\n *\n * @iterable mixed\n */", + (const char *)0x8 /* KindOfNull */, (const char *)0x2000, "iterable", "", (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, "N;", (const char *)2, "null", (const char *)4, NULL, + NULL, + NULL, + NULL, + (const char *)0x10006040, "isEmpty", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.isempty.php )\n *\n * Returns true if the Set is empty, false otherwise.\n *\n * @return bool\n */", + (const char *)0x9 /* KindOfBoolean */, NULL, + NULL, + NULL, + (const char *)0x10006040, "count", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.count.php )\n *\n * Returns the number of values in the Set.\n *\n * @return int\n */", + (const char *)0xa /* KindOfInt64 */, NULL, + NULL, + NULL, + (const char *)0x10006040, "items", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.items.php )\n *\n * Returns an Iterable that produces the values from this Set.\n *\n * @return object\n */", + (const char *)0x40 /* KindOfObject */, NULL, + NULL, + NULL, + (const char *)0x10006040, "view", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.view.php )\n *\n * Returns a lazy iterable view of this Set.\n *\n * @return object\n */", + (const char *)0x40 /* KindOfObject */, NULL, + NULL, + NULL, + (const char *)0x10006040, "clear", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.clear.php )\n *\n * Removes all values from the Set.\n *\n * @return object\n */", + (const char *)0x40 /* KindOfObject */, NULL, + NULL, + NULL, + (const char *)0x10006040, "contains", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.contains.php )\n *\n * Returns true if the specified value is present in the Set, returns\n * false otherwise.\n *\n * @val mixed\n *\n * @return bool\n */", + (const char *)0x9 /* KindOfBoolean */, (const char *)0x2000, "val", "", (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, "", (const char *)0, "", (const char *)0, NULL, + NULL, + NULL, + NULL, + (const char *)0x10006040, "remove", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.remove.php )\n *\n * Removes the specified value from this Set.\n *\n * @val mixed\n *\n * @return object\n */", + (const char *)0x40 /* KindOfObject */, (const char *)0x2000, "val", "", (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, "", (const char *)0, "", (const char *)0, NULL, + NULL, + NULL, + NULL, + (const char *)0x10006040, "discard", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.discard.php )\n *\n * Removes the specified value from this Set.\n *\n * @val mixed\n *\n * @return object\n */", + (const char *)0x40 /* KindOfObject */, (const char *)0x2000, "val", "", (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, "", (const char *)0, "", (const char *)0, NULL, + NULL, + NULL, + NULL, + (const char *)0x10006040, "add", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.add.php )\n *\n * Adds the specified value to this Set.\n *\n * @val mixed\n *\n * @return object\n */", + (const char *)0x40 /* KindOfObject */, (const char *)0x2000, "val", "", (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, "", (const char *)0, "", (const char *)0, NULL, + NULL, + NULL, + NULL, + (const char *)0x10006040, "addAll", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.addall.php )\n *\n * Adds the values produced by the specified Iterable to this Set.\n *\n * @iterable mixed\n *\n * @return object\n */", + (const char *)0x40 /* KindOfObject */, (const char *)0x2000, "iterable", "", (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, "", (const char *)0, "", (const char *)0, NULL, + NULL, + NULL, + NULL, + (const char *)0x10006040, "toArray", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.toarray.php )\n *\n * Returns an array built from the values from this Set.\n *\n * @return map\n */", + (const char *)0x20 /* KindOfArray */, NULL, + NULL, + NULL, + (const char *)0x10006040, "getIterator", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.getiterator.php )\n *\n * Returns an iterator that points to beginning of this Set.\n *\n * @return object\n */", + (const char *)0x40 /* KindOfObject */, NULL, + NULL, + NULL, + (const char *)0x10006040, "map", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.map.php )\n *\n * Returns an Iterable of the values produced by applying the specified\n * callback on the values of this Set.\n *\n * @callback mixed\n *\n * @return object\n */", + (const char *)0x40 /* KindOfObject */, (const char *)0x2000, "callback", "", (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, "", (const char *)0, "", (const char *)0, NULL, + NULL, + NULL, + NULL, + (const char *)0x10006040, "filter", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.filter.php )\n *\n * Returns a Iterable of all the values from this Set for which the\n * specified callback returns true.\n *\n * @callback mixed\n *\n * @return object\n */", + (const char *)0x40 /* KindOfObject */, (const char *)0x2000, "callback", "", (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, "", (const char *)0, "", (const char *)0, NULL, + NULL, + NULL, + NULL, + (const char *)0x10006040, "zip", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.zip.php )\n *\n * Returns a Iterable produced by combined the specified Iterables\n * pair-wise.\n *\n * @iterable mixed\n *\n * @return object\n */", + (const char *)0x40 /* KindOfObject */, (const char *)0x2000, "iterable", "", (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, "", (const char *)0, "", (const char *)0, NULL, + NULL, + NULL, + NULL, + (const char *)0x10006040, "difference", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.difference.php )\n *\n *\n * @iterable mixed\n *\n * @return object\n */", + (const char *)0x40 /* KindOfObject */, (const char *)0x2000, "iterable", "", (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, "", (const char *)0, "", (const char *)0, NULL, + NULL, + NULL, + NULL, + (const char *)0x10006040, "updateFromArrayValues", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.updatefromarrayvalues.php )\n *\n *\n * @arr mixed\n *\n * @return object\n */", + (const char *)0x40 /* KindOfObject */, (const char *)0x2000, "arr", "", (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, "", (const char *)0, "", (const char *)0, NULL, + NULL, + NULL, + NULL, + (const char *)0x10006040, "updateFromIterableValues", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.updatefromiterablevalues.php\n * )\n *\n *\n * @iterable mixed\n *\n * @return object\n */", + (const char *)0x40 /* KindOfObject */, (const char *)0x2000, "iterable", "", (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, "", (const char *)0, "", (const char *)0, NULL, + NULL, + NULL, + NULL, + (const char *)0x10006040, "__toString", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.tostring.php )\n *\n *\n * @return string\n */", + (const char *)0x14 /* KindOfString */, NULL, + NULL, + NULL, + (const char *)0x10006040, "__get", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.get.php )\n *\n *\n * @name mixed\n *\n * @return mixed\n */", + (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, (const char *)0x2000, "name", "", (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, "", (const char *)0, "", (const char *)0, NULL, + NULL, + NULL, + NULL, + (const char *)0x10006040, "__set", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.set.php )\n *\n *\n * @name mixed\n * @value mixed\n *\n * @return mixed\n */", + (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, (const char *)0x2000, "name", "", (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, "", (const char *)0, "", (const char *)0, NULL, + (const char *)0x2000, "value", "", (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, "", (const char *)0, "", (const char *)0, NULL, + NULL, + NULL, + NULL, + (const char *)0x10006040, "__isset", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.isset.php )\n *\n *\n * @name mixed\n *\n * @return bool\n */", + (const char *)0x9 /* KindOfBoolean */, (const char *)0x2000, "name", "", (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, "", (const char *)0, "", (const char *)0, NULL, + NULL, + NULL, + NULL, + (const char *)0x10006040, "__unset", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.unset.php )\n *\n *\n * @name mixed\n *\n * @return mixed\n */", + (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, (const char *)0x2000, "name", "", (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, "", (const char *)0, "", (const char *)0, NULL, + NULL, + NULL, + NULL, + (const char *)0x10006240, "fromItems", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.fromitems.php )\n *\n * Returns a Set built from the values produced by the specified Iterable.\n *\n * @iterable mixed\n *\n * @return object\n */", + (const char *)0x40 /* KindOfObject */, (const char *)0x2000, "iterable", "", (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, "", (const char *)0, "", (const char *)0, NULL, + NULL, + NULL, + NULL, + (const char *)0x10006240, "fromArray", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.fromarray.php )\n *\n * Returns a Set built from the values from the specified array.\n *\n * @arr mixed\n *\n * @return object\n */", + (const char *)0x40 /* KindOfObject */, (const char *)0x2000, "arr", "", (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, "", (const char *)0, "", (const char *)0, NULL, + NULL, + NULL, + NULL, + (const char *)0x10026240, "fromArrays", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.fromarrays.php )\n *\n * Returns a Set built from the values from the specified arrays.\n *\n * @return object\n */", + (const char *)0x40 /* KindOfObject */, NULL, + NULL, + NULL, + (const char *)0x10006240, "fromIterableValues", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/set.fromiterablevalues.php )\n *\n *\n * @iterable mixed\n *\n * @return object\n */", + (const char *)0x40 /* KindOfObject */, (const char *)0x2000, "iterable", "", (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, "", (const char *)0, "", (const char *)0, NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + (const char *)0x10006020, "SetIterator", "", "", (const char *)0, (const char *)0, + "/**\n * ( excerpt from http://php.net/manual/en/class.setiterator.php )\n *\n * An iterator implementation for iterating over a Set.\n *\n */", + "iterator", NULL, + (const char *)0x10006040, "__construct", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/setiterator.construct.php )\n *\n *\n */", + (const char *)0x8 /* KindOfNull */, NULL, + NULL, + NULL, + (const char *)0x10006040, "current", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/setiterator.current.php )\n *\n * Returns the current value that the iterator points to.\n *\n * @return mixed\n */", + (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, NULL, + NULL, + NULL, + (const char *)0x10006040, "key", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/setiterator.key.php )\n *\n *\n * @return mixed\n */", + (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, NULL, + NULL, + NULL, + (const char *)0x10006040, "valid", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/setiterator.valid.php )\n *\n * Returns true if the iterator points to a valid value, returns false\n * otherwise.\n *\n * @return bool\n */", + (const char *)0x9 /* KindOfBoolean */, NULL, + NULL, + NULL, + (const char *)0x10006040, "next", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/setiterator.next.php )\n *\n * Advance this iterator forward one position.\n *\n */", + (const char *)0x8 /* KindOfNull */, NULL, + NULL, + NULL, + (const char *)0x10006040, "rewind", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/setiterator.rewind.php )\n *\n * Move this iterator back to the first position.\n *\n */", + (const char *)0x8 /* KindOfNull */, NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, (const char *)0x10006020, "Pair", "", "", (const char *)0, (const char *)0, "/**\n * ( excerpt from http://php.net/manual/en/class.pair.php )\n *\n * An ordered fixed-sized container.\n *\n */", "constvector", NULL, diff --git a/hphp/system/classes/collections.php b/hphp/system/classes/collections.php index f6dea69db..34c3e4070 100644 --- a/hphp/system/classes/collections.php +++ b/hphp/system/classes/collections.php @@ -57,8 +57,7 @@ class MappedIterator implements Iterator { $this->it->next(); } public function key() { - throw new RuntimeException( - "Call to undefined method MappedIterator::key()"); + return null; } public function current() { return ($this->fn)($this->it->current()); @@ -154,8 +153,7 @@ class FilteredIterator implements Iterator { } } public function key() { - throw new RuntimeException( - "Call to undefined method FilteredIterator::key()"); + return null; } public function current() { return $this->it->current(); @@ -254,8 +252,7 @@ class ZippedIterator implements Iterator { $this->it2->next(); } public function key() { - throw new RuntimeException( - "Call to undefined method ZippedIterator::key()"); + return null; } public function current() { return Pair {$this->it1->current(), $this->it2->current()}; @@ -344,8 +341,7 @@ class KeysIterator implements Iterator { $this->it->next(); } public function key() { - throw new RuntimeException( - "Call to undefined method KeysIterator::key()"); + return null; } public function current() { return $this->it->key(); @@ -384,8 +380,7 @@ class KVZippedIterator implements Iterator { $this->it->next(); } public function key() { - throw new RuntimeException( - "Call to undefined method KVZippedIterator::key()"); + return null; } public function current() { return Pair {$this->it->key(), $this->it->current()}; diff --git a/hphp/test/slow/collection_classes/836.php b/hphp/test/slow/collection_classes/836.php new file mode 100644 index 000000000..547599c51 --- /dev/null +++ b/hphp/test/slow/collection_classes/836.php @@ -0,0 +1,11 @@ +map($mapFn)->filter($filtFn)); +foreach ($st->view()->map($mapFn)->filter($filtFn) as $v) { + var_dump($v); +} +$st = new Set(Vector {6, 9}); +var_dump($st->map($mapFn)->filter($filtFn)); +foreach ($st->items()->map($mapFn)->filter($filtFn) as $v) { + var_dump($v); +} + diff --git a/hphp/test/slow/collection_classes/837.php.expect b/hphp/test/slow/collection_classes/837.php.expect new file mode 100644 index 000000000..65823a638 --- /dev/null +++ b/hphp/test/slow/collection_classes/837.php.expect @@ -0,0 +1,8 @@ +object(Set)#5 (1) { + int(4) +} +int(4) +object(Set)#12 (1) { + int(10) +} +int(10) diff --git a/hphp/test/slow/collection_classes/838.php b/hphp/test/slow/collection_classes/838.php new file mode 100644 index 000000000..499f8eae3 --- /dev/null +++ b/hphp/test/slow/collection_classes/838.php @@ -0,0 +1,43 @@ +add('a'); +var_dump($s1 == $s2); +$s2[] = 'b'; +var_dump($s1 == $s2); +$s1[] = 'b'; +var_dump($s1 == $s2); +$s2->add('a'); +var_dump($s1 == $s2); +$s1[] = 'c'; +var_dump($s1 == $s2); +echo "============\n"; +$s1 = Set {'a', 'b', 'c', 'd'}; +$s1->remove('a'); +$s1->remove('c'); +$s2 = Set {'b', 'd'}; +var_dump($s1 == $s2); +$s1->remove('d'); +var_dump($s1 == $s2); +$s2->remove('d'); +var_dump($s1 == $s2); +$s1->add('d'); +var_dump($s1 == $s2); +$s2->add('d'); +var_dump($s1 == $s2); +echo "============\n"; +$m = Set {}; +var_dump($m == null); +var_dump($m == false); +var_dump($m == true); +var_dump($m == 1); +var_dump($m == "Set"); +echo "============\n"; +$m = Set {7}; +var_dump($m == null); +var_dump($m == false); +var_dump($m == true); +var_dump($m == 1); +var_dump($m == "Set"); + diff --git a/hphp/test/slow/collection_classes/838.php.expect b/hphp/test/slow/collection_classes/838.php.expect new file mode 100644 index 000000000..81f1d9b9d --- /dev/null +++ b/hphp/test/slow/collection_classes/838.php.expect @@ -0,0 +1,24 @@ +bool(true) +bool(false) +bool(false) +bool(false) +bool(true) +bool(false) +============ +bool(true) +bool(false) +bool(true) +bool(false) +bool(true) +============ +bool(false) +bool(false) +bool(true) +bool(false) +bool(false) +============ +bool(false) +bool(false) +bool(true) +bool(false) +bool(false) diff --git a/hphp/test/slow/collection_classes/839.php b/hphp/test/slow/collection_classes/839.php new file mode 100644 index 000000000..c821dbc40 --- /dev/null +++ b/hphp/test/slow/collection_classes/839.php @@ -0,0 +1,22 @@ + 1, 'b' => $obj, 33 => "foo", 44 => $obj}; +apc_store('x0', $x0); +$x1 = apc_fetch('x0'); +var_dump($x1); +echo "========\n"; +$x0 = StableMap {'a' => 1, 'b' => $obj, 33 => "foo", 44 => $obj}; +apc_store('x0', $x0); +$x1 = apc_fetch('x0'); +var_dump($x1); +echo "========\n"; +$x0 = Set {1, "foo"}; +apc_store('x0', $x0); +$x1 = apc_fetch('x0'); +var_dump($x1); + diff --git a/hphp/test/slow/collection_classes/839.php.expect b/hphp/test/slow/collection_classes/839.php.expect new file mode 100644 index 000000000..ed67ec2a4 --- /dev/null +++ b/hphp/test/slow/collection_classes/839.php.expect @@ -0,0 +1,43 @@ +object(Vector)#3 (4) { + [0]=> + int(1) + [1]=> + object(stdClass)#4 (0) { + } + [2]=> + string(3) "foo" + [3]=> + object(stdClass)#4 (0) { + } +} +======== +object(Map)#6 (4) { + [33]=> + string(3) "foo" + ["a"]=> + int(1) + [44]=> + object(stdClass)#7 (0) { + } + ["b"]=> + object(stdClass)#7 (0) { + } +} +======== +object(StableMap)#9 (4) { + ["a"]=> + int(1) + ["b"]=> + object(stdClass)#10 (0) { + } + [33]=> + string(3) "foo" + [44]=> + object(stdClass)#10 (0) { + } +} +======== +object(Set)#12 (2) { + int(1) + string(3) "foo" +} diff --git a/hphp/test/slow/collection_classes/840.php b/hphp/test/slow/collection_classes/840.php new file mode 100644 index 000000000..6af604d1c --- /dev/null +++ b/hphp/test/slow/collection_classes/840.php @@ -0,0 +1,8 @@ + 4}; +$x0 = Vector {Map {'a' => 1}, Map {'b' => 2}, Set {'c'}, $m, $m}; +apc_store('x0', $x0); +$x1 = apc_fetch('x0'); +$x1[3]['e'] = 5; +var_dump($x1); + diff --git a/hphp/test/slow/collection_classes/840.php.expect b/hphp/test/slow/collection_classes/840.php.expect new file mode 100644 index 000000000..24511b6fe --- /dev/null +++ b/hphp/test/slow/collection_classes/840.php.expect @@ -0,0 +1,30 @@ +object(Vector)#6 (5) { + [0]=> + object(Map)#7 (1) { + ["a"]=> + int(1) + } + [1]=> + object(Map)#8 (1) { + ["b"]=> + int(2) + } + [2]=> + object(Set)#9 (1) { + string(1) "c" + } + [3]=> + object(StableMap)#10 (2) { + ["d"]=> + int(4) + ["e"]=> + int(5) + } + [4]=> + object(StableMap)#10 (2) { + ["d"]=> + int(4) + ["e"]=> + int(5) + } +} diff --git a/hphp/test/zend/good/zend/bug40833.php b/hphp/test/zend/bad/zend/bug40833.php similarity index 100% rename from hphp/test/zend/good/zend/bug40833.php rename to hphp/test/zend/bad/zend/bug40833.php diff --git a/hphp/test/zend/good/zend/bug40833.php.expectf b/hphp/test/zend/bad/zend/bug40833.php.expectf similarity index 100% rename from hphp/test/zend/good/zend/bug40833.php.expectf rename to hphp/test/zend/bad/zend/bug40833.php.expectf