Permalink
Cannot retrieve contributors at this time
/* | |
+----------------------------------------------------------------------+ | |
| HipHop for PHP | | |
+----------------------------------------------------------------------+ | |
| Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) | | |
+----------------------------------------------------------------------+ | |
| This source file is subject to version 3.01 of the PHP license, | | |
| that is bundled with this package in the file LICENSE, and is | | |
| available through the world-wide-web at the following url: | | |
| http://www.php.net/license/3_01.txt | | |
| If you did not receive a copy of the PHP license and are unable to | | |
| obtain it through the world-wide-web, please send a note to | | |
| license@php.net so we can mail you a copy immediately. | | |
+----------------------------------------------------------------------+ | |
*/ | |
#pragma once | |
#include "hphp/runtime/base/array-data.h" | |
#include "hphp/runtime/base/record-data.h" | |
#include "hphp/runtime/base/tv-conversions.h" | |
#include "hphp/runtime/base/tv-mutate.h" | |
#include "hphp/runtime/base/tv-refcount.h" | |
#include "hphp/runtime/base/tv-val.h" | |
#include "hphp/runtime/base/tv-variant.h" | |
#include "hphp/runtime/base/type-object.h" | |
#include "hphp/runtime/base/type-resource.h" | |
#include "hphp/runtime/base/type-string.h" | |
#include "hphp/runtime/base/typed-value.h" | |
#include "hphp/runtime/vm/class-meth-data-ref.h" | |
#include "hphp/runtime/vm/rclass-meth-data.h" | |
#include "hphp/runtime/vm/rfunc-data.h" | |
#include <algorithm> | |
#include <type_traits> | |
namespace HPHP { | |
/////////////////////////////////////////////////////////////////////////////// | |
// Forward declare these to avoid including tv-conversions.h which has a | |
// circular dependency with this file. | |
struct OptionalVariant; | |
/* | |
* These structs are substitutes for Variant& or const Variant&, when there's | |
* no actual Variant in memory to take a reference to (when working with a | |
* tv_lval from an Array, for example). They should be treated with the same | |
* care as normal references: when copying or storing a variant_ref or | |
* const_variant_ref, think carefully about the lifetime of the underlying | |
* data. | |
*/ | |
namespace variant_ref_detail { | |
template<bool is_const> | |
struct base { | |
private: | |
using tv_val_t = typename std::conditional<is_const, tv_rval, tv_lval>::type; | |
public: | |
explicit base(tv_val_t val) : m_val{val} {} | |
DataType getType() const { | |
return type(m_val); | |
} | |
bool isNull() const { return isNullType(getType()); } | |
bool isBoolean() const { return isBooleanType(getType()); } | |
bool isInteger() const { return isIntType(getType()); } | |
bool isDouble() const { return isDoubleType(getType()); } | |
bool isString() const { return isStringType(getType()); } | |
bool isArray() const { return isArrayLikeType(getType()); } | |
bool isPHPArray() const { return isArrayType(getType()); } | |
bool isVec() const { return isVecType(getType()); } | |
bool isDict() const { return isDictType(getType()); } | |
bool isKeyset() const { return isKeysetType(getType()); } | |
bool isHackArray() const { return isHackArrayType(getType()); } | |
bool isObject() const { return isObjectType(getType()); } | |
bool isResource() const { return isResourceType(getType()); } | |
bool isFunc() const { return isFuncType(getType()); } | |
bool isClass() const { return isClassType(getType()); } | |
bool isLazyClass() const { return isLazyClassType(getType()); } | |
bool isClsMeth() const { return isClsMethType(getType()); } | |
bool isPrimitive() const { return !isRefcountedType(type(m_val)); } | |
auto toBoolean() const { return tvCastToBoolean(*m_val); } | |
auto toInt64() const { return tvCastToInt64(*m_val); } | |
auto toDouble() const { return tvCastToDouble(*m_val); } | |
auto toString(ConvNoticeLevel level = ConvNoticeLevel::None, | |
const StringData* notice_reason = nullptr) const { | |
if (isStringType(type(m_val))) return String{val(m_val).pstr}; | |
return String::attach(tvCastToStringData(*m_val, level, notice_reason)); | |
} | |
auto toArray() const { | |
if (isArrayLikeType(type(m_val))) return Array{val(m_val).parr}; | |
return Array::attach(tvCastToArrayLikeData<IntishCast::None>(*m_val)); | |
} | |
auto toObject() const { | |
if (isObjectType(type(m_val))) return Object{val(m_val).pobj}; | |
return Object::attach(tvCastToObjectData(*m_val)); | |
} | |
auto& asCStrRef() const { return HPHP::asCStrRef(m_val); } | |
auto& asCArrRef() const { return HPHP::asCArrRef(m_val); } | |
auto& asCObjRef() const { return HPHP::asCObjRef(m_val); } | |
tv_rval asTypedValue() const { return m_val; } | |
ArrayData *getArrayData() const { | |
assertx(isArray()); | |
return val(m_val).parr; | |
} | |
auto toFuncVal() const { | |
assertx(isFunc()); | |
return val(m_val).pfunc; | |
} | |
auto toClassVal() const { | |
assertx(isClass()); | |
return val(m_val).pclass; | |
} | |
auto toLazyClassVal() const { | |
assertx(isLazyClass()); | |
return val(m_val).plazyclass; | |
} | |
ClsMethDataRef toClsMethVal() const { | |
assertx(isClsMeth()); | |
return val(m_val).pclsmeth; | |
} | |
RefCount getRefCount() const noexcept { | |
return isRefcountedType(type(m_val)) || hasPersistentFlavor(type(m_val)) | |
? val(m_val).pcnt->count() | |
: OneReference; | |
} | |
protected: | |
const tv_val_t m_val; | |
}; | |
} | |
struct const_variant_ref; | |
struct variant_ref : variant_ref_detail::base<false> { | |
using variant_ref_detail::base<false>::base; | |
/* implicit */ variant_ref(Variant& v); | |
/* implicit */ operator const_variant_ref() const; | |
tv_lval lval() const { return m_val; } | |
void unset() const { | |
tvMove(make_tv<KindOfUninit>(), m_val); | |
} | |
void setNull() noexcept { | |
tvSetNull(m_val); | |
} | |
variant_ref& operator=(const Variant& v) noexcept; | |
variant_ref& assign(const Variant& v) noexcept; | |
variant_ref& operator=(Variant &&rhs) noexcept; | |
// Generic assignment operator. Forward argument (preserving rvalue-ness and | |
// lvalue-ness) to the appropriate set function, as long as its not a Variant. | |
template <typename T> | |
typename std::enable_if< | |
!std::is_same< | |
Variant, | |
typename std::remove_reference<typename std::remove_cv<T>::type>::type | |
>::value | |
&& | |
!std::is_same< | |
VarNR, | |
typename std::remove_reference<typename std::remove_cv<T>::type>::type | |
>::value, | |
variant_ref& | |
>::type operator=(T&& v) { | |
set(std::forward<T>(v)); | |
return *this; | |
} | |
void set(bool v) noexcept; | |
void set(int v) noexcept; | |
void set(int64_t v) noexcept; | |
void set(double v) noexcept; | |
void set(const char* v) = delete; | |
void set(const std::string & v) { | |
return set(String(v)); | |
} | |
void set(StringData *v) noexcept; | |
void set(ArrayData *v) noexcept; | |
void set(ObjectData *v) noexcept; | |
void set(ResourceHdr *v) noexcept; | |
void set(ResourceData *v) noexcept { set(v->hdr()); } | |
void set(const StringData *v) = delete; | |
void set(const ArrayData *v) = delete; | |
void set(const ObjectData *v) = delete; | |
void set(const ResourceData *v) = delete; | |
void set(const ResourceHdr *v) = delete; | |
void set(const String& v) noexcept { set(v.get()); } | |
void set(const StaticString & v) noexcept; | |
void set(const Array& v) noexcept { set(v.get()); } | |
void set(const Object& v) noexcept { set(v.get()); } | |
void set(const Resource& v) noexcept { set(v.hdr()); } | |
void set(String&& v) noexcept { steal(v.detach()); } | |
void set(Array&& v) noexcept { steal(v.detach()); } | |
void set(Object&& v) noexcept { steal(v.detach()); } | |
void set(Resource&& v) noexcept { steal(v.detachHdr()); } | |
template<typename T> | |
void set(const req::ptr<T> &v) noexcept { | |
return set(v.get()); | |
} | |
template <typename T> | |
void set(req::ptr<T>&& v) noexcept { | |
return steal(v.detach()); | |
} | |
void steal(StringData* v) noexcept; | |
void steal(ArrayData* v) noexcept; | |
void steal(ObjectData* v) noexcept; | |
void steal(ResourceHdr* v) noexcept; | |
void steal(ResourceData* v) noexcept { steal(v->hdr()); } | |
}; | |
struct const_variant_ref : variant_ref_detail::base<true> { | |
using variant_ref_detail::base<true>::base; | |
/* implicit */ const_variant_ref(const Variant& v); | |
tv_rval rval() const { return m_val; } | |
}; | |
inline variant_ref::operator const_variant_ref() const { | |
return const_variant_ref{m_val}; | |
} | |
/* | |
* This class predates HHVM. | |
* | |
* In hphpc, when type inference failed to know type of a variable, we | |
* would use Variant to represent the php variable in generated C++. | |
* | |
* Now Variant is only used in C++ extensions, and the API is mostly | |
* legacy stuff. If you're writing a C++ extension, try to avoid | |
* Variant when you can (but you often can't, and we don't really have | |
* a replacement yet, sorry). | |
* | |
* In C++ extensions, this class can be used as a generic handle to | |
* one of our other data types (e.g. StringData, ArrayData) | |
* | |
* Beware: | |
* | |
* For historical reasons, this class does a lot of things you | |
* don't really expect in a well-behaved C++ class. | |
* | |
* For example, the copy constructor is not a copy constructor (it converts | |
* KindOfUninit to KindOfNull). A similar story applies to the move | |
* constructor. (And this means we may actually rely on whether copy | |
* elision (NRVO etc) is happening in some places for correctness.) | |
* | |
* Use carefully. | |
* | |
*/ | |
struct Variant : private TypedValue { | |
friend variant_ref; | |
// Used by VariantTraits to create a folly::Optional-like | |
// optional Variant which fits in 16 bytes. | |
using Optional = OptionalVariant; | |
enum class NullInit {}; | |
enum class NoInit {}; | |
enum class TVCopy {}; | |
enum class TVDup {}; | |
enum class ArrayInitCtor {}; | |
enum class Attach {}; | |
enum class Wrap {}; | |
Variant() noexcept { m_type = KindOfUninit; } | |
explicit Variant(NullInit) noexcept { m_type = KindOfNull; } | |
explicit Variant(NoInit) noexcept {} | |
/* implicit */ Variant(bool v) noexcept { | |
m_type = KindOfBoolean; m_data.num = v; | |
} | |
/* implicit */ Variant(int v) noexcept { | |
m_type = KindOfInt64; m_data.num = v; | |
} | |
// The following two overloads will accept int64_t whether it's | |
// implemented as long or long long. | |
/* implicit */ Variant(long v) noexcept { | |
m_type = KindOfInt64; m_data.num = v; | |
} | |
/* implicit */ Variant(long long v) noexcept { | |
m_type = KindOfInt64; m_data.num = v; | |
} | |
/* implicit */ Variant(unsigned v) noexcept { | |
m_type = KindOfInt64; m_data.num = v; | |
} | |
/* implicit */ Variant(unsigned long v) noexcept { | |
m_type = KindOfInt64; m_data.num = v; | |
} | |
/* implicit */ Variant(unsigned long long v) noexcept { | |
m_type = KindOfInt64; m_data.num = v; | |
} | |
/* implicit */ Variant(double v) noexcept { | |
m_type = KindOfDouble; m_data.dbl = v; | |
} | |
/* implicit */ Variant(const char* v) { | |
m_type = KindOfString; | |
m_data.pstr = StringData::Make(v); | |
} | |
/* implicit */ Variant(const std::string &v) { | |
m_type = KindOfString; | |
StringData *s = StringData::Make(v.c_str(), v.size(), CopyString); | |
assertx(s); | |
m_data.pstr = s; | |
} | |
/* implicit */ Variant(const StaticString &v) noexcept { | |
assertx(v.get() && !v.get()->isRefCounted()); | |
m_type = KindOfPersistentString; | |
m_data.pstr = v.get(); | |
} | |
/* implicit */ Variant(const String& v) noexcept : Variant(v.get()) {} | |
/* implicit */ Variant(const Array& v) noexcept : Variant(v.get()) { } | |
/* implicit */ Variant(const Object& v) noexcept : Variant(v.get()) {} | |
/* implicit */ Variant(const Resource& v) noexcept | |
: Variant(v.hdr()) {} | |
/* implicit */ Variant(Class* v) { | |
m_type = KindOfClass; | |
m_data.pclass = v; | |
} | |
/* implicit */ Variant(LazyClassData v) { | |
m_type = KindOfLazyClass; | |
m_data.plazyclass = v; | |
} | |
/* implicit */ Variant(const ClsMethDataRef v) { | |
m_type = KindOfClsMeth; | |
m_data.pclsmeth = v; | |
incRefClsMeth(v); | |
} | |
/* | |
* Explicit conversion constructors. These all manipulate ref-counts of bare | |
* pointers as a side-effect, so we want to be explicit when its happening. | |
*/ | |
explicit Variant(StringData* v) noexcept; | |
explicit Variant(ArrayData* v) noexcept { | |
if (v) { | |
m_data.parr = v; | |
if (v->isRefCounted()) { | |
m_type = v->toDataType(); | |
v->rawIncRefCount(); | |
} else { | |
m_type = v->toPersistentDataType(); | |
} | |
} else { | |
m_type = KindOfNull; | |
} | |
} | |
explicit Variant(ObjectData* v) noexcept { | |
if (v) { | |
m_type = KindOfObject; | |
m_data.pobj = v; | |
v->incRefCount(); | |
} else { | |
m_type = KindOfNull; | |
} | |
} | |
explicit Variant(const Func* f) noexcept { | |
assertx(f); | |
m_type = KindOfFunc; | |
m_data.pfunc = f; | |
} | |
explicit Variant(RFuncData* v) noexcept { | |
if (v) { | |
assertx(v); | |
m_type = KindOfRFunc; | |
m_data.prfunc = v; | |
v->incRefCount(); | |
} else { | |
m_type = KindOfNull; | |
} | |
} | |
explicit Variant(RClsMethData* v) noexcept { | |
if (v) { | |
assertx(v); | |
m_type = KindOfRClsMeth; | |
m_data.prclsmeth = v; | |
v->incRefCount(); | |
} else { | |
m_type = KindOfNull; | |
} | |
} | |
template <typename T> | |
explicit Variant(const req::ptr<T>& ptr) : Variant(ptr.get()) { } | |
template <typename T> | |
explicit Variant(req::ptr<T>&& ptr) noexcept | |
: Variant(ptr.detach(), Attach{}) { } | |
/* | |
* Creation constructor from ArrayInit that avoids a null check and an | |
* inc-ref. | |
*/ | |
explicit Variant(ArrayData* ad, DataType dt, ArrayInitCtor) noexcept { | |
assertx(ad->toDataType() == dt); | |
m_type = dt; | |
m_data.parr = ad; | |
} | |
enum class PersistentArrInit {}; | |
Variant(const ArrayData* ad, DataType dt, PersistentArrInit) noexcept { | |
// TODO(T58820726): Switch back to trusting caller and strict equality. | |
assertx(equivDataTypes(ad->toPersistentDataType(), dt)); | |
assertx(!ad->isRefCounted()); | |
m_data.parr = const_cast<ArrayData*>(ad); | |
m_type = dt; | |
} | |
enum class PersistentStrInit {}; | |
explicit Variant(const StringData *s, PersistentStrInit) noexcept { | |
assertx(!s->isRefCounted()); | |
m_data.pstr = const_cast<StringData*>(s); | |
m_type = KindOfPersistentString; | |
} | |
// These are prohibited, but declared just to prevent accidentally | |
// calling the bool constructor just because we had a pointer to | |
// const. | |
/* implicit */ Variant(const void*) = delete; | |
template<typename Ret, typename... Args> | |
/* implicit */ Variant(Ret (*)(Args...)) = delete; | |
template<class Class, typename Ret, typename... Args> | |
/* implicit */ Variant(Ret (Class::*)(Args...)) = delete; | |
template<class Class, typename Ret, typename... Args> | |
/* implicit */ Variant(Ret (Class::*)(Args...) const) = delete; | |
////////////////////////////////////////////////////////////////////// | |
/* | |
* Copy constructor and copy assignment do not semantically make | |
* copies: they turn uninits to null. | |
*/ | |
Variant(const Variant& v) noexcept; | |
explicit Variant(const_variant_ref v) noexcept; | |
Variant(const Variant& v, TVCopy) noexcept { | |
m_type = v.m_type; | |
m_data = v.m_data; | |
} | |
Variant(const Variant& v, TVDup) noexcept { | |
m_type = v.m_type; | |
m_data = v.m_data; | |
tvIncRefGen(*asTypedValue()); | |
} | |
Variant& operator=(const Variant& v) noexcept { | |
return assign(v); | |
} | |
/* | |
* Move ctors | |
* | |
* Note: not semantically moves. Like our "copy constructor", these | |
* turn uninits to null. | |
*/ | |
Variant(Variant&& v) noexcept { | |
assertx(this != &v); | |
if (v.m_type != KindOfUninit) { | |
m_type = v.m_type; | |
m_data = v.m_data; | |
v.m_type = KindOfNull; | |
} else { | |
m_type = KindOfNull; | |
} | |
} | |
// Move ctor for strings | |
/* implicit */ Variant(String&& v) noexcept { | |
StringData *s = v.get(); | |
if (LIKELY(s != nullptr)) { | |
m_data.pstr = s; | |
m_type = s->isRefCounted() ? KindOfString : KindOfPersistentString; | |
v.detach(); | |
} else { | |
m_type = KindOfNull; | |
} | |
} | |
// Move ctor for arrays | |
/* implicit */ Variant(Array&& v) noexcept { | |
ArrayData *a = v.get(); | |
if (LIKELY(a != nullptr)) { | |
m_data.parr = a; | |
m_type = a->isRefCounted() ? a->toDataType() : a->toPersistentDataType(); | |
v.detach(); | |
} else { | |
m_type = KindOfNull; | |
} | |
} | |
// Move ctor for objects | |
/* implicit */ Variant(Object&& v) noexcept { | |
ObjectData *pobj = v.get(); | |
if (pobj) { | |
m_type = KindOfObject; | |
m_data.pobj = pobj; | |
v.detach(); | |
} else { | |
m_type = KindOfNull; | |
} | |
} | |
// Move ctor for resources | |
/* implicit */ Variant(Resource&& v) noexcept { | |
auto hdr = v.hdr(); | |
if (hdr) { | |
m_type = KindOfResource; | |
m_data.pres = hdr; | |
v.detachHdr(); | |
} else { | |
m_type = KindOfNull; | |
} | |
} | |
/* | |
* Move assign | |
* | |
* Note: not semantically moves. Like our "copies", these turn uninits | |
* to null. | |
*/ | |
Variant& operator=(Variant &&rhs) noexcept { | |
assertx(this != &rhs); // we end up as null on a self move-assign. | |
Variant& lhs = *this; | |
Variant goner((NoInit())); | |
goner.m_data = lhs.m_data; | |
goner.m_type = lhs.m_type; | |
if (rhs.m_type == KindOfUninit) { | |
lhs.m_type = KindOfNull; | |
} else { | |
lhs.m_type = rhs.m_type; | |
lhs.m_data = rhs.m_data; | |
rhs.m_type = KindOfNull; | |
} | |
return *this; | |
} | |
ALWAYS_INLINE ~Variant() noexcept { | |
tvDecRefGen(asTypedValue()); | |
if (debug) { | |
memset(this, kTVTrashFill2, sizeof(*this)); | |
} | |
} | |
////////////////////////////////////////////////////////////////////// | |
/* | |
* During sweeping, request-allocated things are not allowed to be decref'd | |
* or manipulated. This function is used to cause a Variant to go | |
* into a state where its destructor will have no effects on the | |
* request local heap, in cases where sweepable objects can't | |
* organize things to avoid running Variant destructors. | |
*/ | |
void releaseForSweep() { m_type = KindOfNull; } | |
////////////////////////////////////////////////////////////////////// | |
/** | |
* Break bindings and set to uninit. | |
*/ | |
void unset() { | |
auto const old = *asTypedValue(); | |
m_type = KindOfUninit; | |
tvDecRefGen(old); | |
} | |
/** | |
* set to null without breaking bindings (if any), faster than v_a = null; | |
*/ | |
void setNull() noexcept { | |
tvSetNull(*asTypedValue()); | |
} | |
static Variant attach(TypedValue tv) noexcept { | |
return Variant{tv, Attach{}}; | |
} | |
static Variant attach(StringData* var) noexcept { | |
return Variant{var, Attach{}}; | |
} | |
static Variant attach(ArrayData* var) noexcept { | |
return Variant{var, Attach{}}; | |
} | |
static Variant attach(ObjectData* var) noexcept { | |
return Variant{var, Attach{}}; | |
} | |
static Variant attach(ResourceData* var) noexcept { | |
return Variant{var, Attach{}}; | |
} | |
static Variant attach(ResourceHdr* var) noexcept { | |
return Variant{var, Attach{}}; | |
} | |
static Variant wrap(TypedValue tv) noexcept { | |
return Variant{tv, Wrap{}}; | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
// int64 | |
ALWAYS_INLINE int64_t asInt64Val() const { | |
assertx(m_type == KindOfInt64); | |
return m_data.num; | |
} | |
ALWAYS_INLINE int64_t toInt64Val() const { | |
assertx(is(KindOfInt64)); | |
return m_data.num; | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
// double | |
ALWAYS_INLINE double asDoubleVal() const { | |
assertx(m_type == KindOfDouble); | |
return m_data.dbl; | |
} | |
ALWAYS_INLINE double toDoubleVal() const { | |
assertx(is(KindOfDouble)); | |
return m_data.dbl; | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
// boolean | |
ALWAYS_INLINE bool asBooleanVal() const { | |
assertx(m_type == KindOfBoolean); | |
return m_data.num; | |
} | |
ALWAYS_INLINE bool toBooleanVal() const { | |
assertx(is(KindOfBoolean)); | |
return m_data.num; | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
// string | |
ALWAYS_INLINE const String& asCStrRef() const { | |
assertx(isStringType(m_type) && m_data.pstr); | |
return *reinterpret_cast<const String*>(&m_data.pstr); | |
} | |
ALWAYS_INLINE String& asStrRef() { | |
assertx(isStringType(m_type) && m_data.pstr); | |
// The caller is likely going to modify the string, so we have to eagerly | |
// promote KindOfPersistentString -> KindOfString. | |
m_type = KindOfString; | |
return *reinterpret_cast<String*>(&m_data.pstr); | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
// array | |
ALWAYS_INLINE const Array& asCArrRef() const { | |
assertx(isArrayLikeType(m_type) && m_data.parr); | |
return *reinterpret_cast<const Array*>(&m_data.parr); | |
} | |
ALWAYS_INLINE Array& asArrRef() { | |
assertx(isArrayLikeType(m_type) && m_data.parr); | |
m_type = m_data.parr->toDataType(); | |
return *reinterpret_cast<Array*>(&m_data.parr); | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
// object | |
ALWAYS_INLINE const Object& asCObjRef() const { | |
assertx(m_type == KindOfObject && m_data.pobj); | |
return *reinterpret_cast<const Object*>(&m_data.pobj); | |
} | |
ALWAYS_INLINE Object & asObjRef() { | |
assertx(m_type == KindOfObject && m_data.pobj); | |
return *reinterpret_cast<Object*>(&m_data.pobj); | |
} | |
ALWAYS_INLINE const Resource& asCResRef() const { | |
assertx(m_type == KindOfResource && m_data.pres); | |
return *reinterpret_cast<const Resource*>(&m_data.pres); | |
} | |
ALWAYS_INLINE const Resource& toCResRef() const { | |
assertx(is(KindOfResource)); | |
assertx(m_data.pres); | |
return *reinterpret_cast<const Resource*>(&m_data.pres); | |
} | |
ALWAYS_INLINE Resource & asResRef() { | |
assertx(m_type == KindOfResource && m_data.pres); | |
return *reinterpret_cast<Resource*>(&m_data.pres); | |
} | |
/** | |
* Type testing functions | |
*/ | |
DataType getType() const { | |
return m_type; | |
} | |
bool is(DataType type) const { | |
return getType() == type; | |
} | |
bool isInitialized() const { | |
return m_type != KindOfUninit; | |
} | |
bool isNull() const { | |
return isNullType(getType()); | |
} | |
bool isBoolean() const { | |
return getType() == KindOfBoolean; | |
} | |
bool isInteger() const { | |
return getType() == KindOfInt64; | |
} | |
bool isDouble() const { | |
return getType() == KindOfDouble; | |
} | |
bool isString() const { | |
return isStringType(getType()); | |
} | |
bool isArray() const { | |
return isArrayLikeType(getType()); | |
} | |
bool isPHPArray() const { | |
return isArrayType(getType()); | |
} | |
bool isDArray() const { | |
return isArrayLikeType(getType()) && m_data.parr->isDArray(); | |
} | |
bool isVArray() const { | |
return isArrayLikeType(getType()) && m_data.parr->isVArray(); | |
} | |
bool isVec() const { | |
return isVecType(getType()); | |
} | |
bool isDict() const { | |
return isDictType(getType()); | |
} | |
bool isKeyset() const { | |
return isKeysetType(getType()); | |
} | |
bool isHackArray() const { | |
return isHackArrayType(getType()); | |
} | |
bool isObject() const { | |
return getType() == KindOfObject; | |
} | |
bool isResource() const { | |
return getType() == KindOfResource; | |
} | |
bool isFunc() const { | |
return isFuncType(getType()); | |
} | |
bool isClass() const { | |
return isClassType(getType()); | |
} | |
bool isLazyClass() const { | |
return isLazyClassType(getType()); | |
} | |
bool isClsMeth() const { | |
return isClsMethType(getType()); | |
} | |
bool isNumeric(bool checkString = false) const noexcept; | |
DataType toNumeric(int64_t &ival, double &dval, bool checkString = false) | |
const; | |
bool isScalar() const noexcept; | |
bool isIntVal() const { | |
switch (m_type) { | |
case KindOfUninit: | |
case KindOfNull: | |
case KindOfBoolean: | |
case KindOfInt64: | |
case KindOfObject: | |
case KindOfResource: | |
return true; | |
case KindOfDouble: | |
case KindOfPersistentString: | |
case KindOfString: | |
case KindOfPersistentVec: | |
case KindOfVec: | |
case KindOfPersistentDict: | |
case KindOfDict: | |
case KindOfPersistentKeyset: | |
case KindOfKeyset: | |
case KindOfPersistentDArray: | |
case KindOfDArray: | |
case KindOfPersistentVArray: | |
case KindOfVArray: | |
case KindOfRFunc: | |
case KindOfFunc: | |
case KindOfClass: | |
case KindOfLazyClass: | |
case KindOfClsMeth: | |
case KindOfRClsMeth: | |
case KindOfRecord: | |
return false; | |
} | |
not_reached(); | |
} | |
// Is "define('CONSTANT', <this value>)" legal? | |
enum class AllowedAsConstantValue { | |
Allowed | |
, NotAllowed | |
, ContainsObject // Allowed if constant of an "enum class". | |
}; | |
AllowedAsConstantValue isAllowedAsConstantValue() const; | |
/** | |
* Get reference count of weak or strong binding. For debugging purpose. | |
*/ | |
int getRefCount() const noexcept { | |
return const_variant_ref{*this}.getRefCount(); | |
} | |
bool getBoolean() const { | |
assertx(getType() == KindOfBoolean); | |
return m_data.num; | |
} | |
int64_t getInt64() const { | |
assertx(getType() == KindOfInt64); | |
return m_data.num; | |
} | |
double getDouble() const { | |
assertx(getType() == KindOfDouble); | |
return m_data.dbl; | |
} | |
/** | |
* Operators | |
*/ | |
Variant& assign(const Variant& v) noexcept { | |
tvSet(tvToInit(*v.asTypedValue()), *asTypedValue()); | |
return *this; | |
} | |
// Generic assignment operator. Forward argument (preserving rvalue-ness and | |
// lvalue-ness) to the appropriate set function, as long as its not a Variant. | |
template <typename T> | |
typename std::enable_if< | |
!std::is_same< | |
Variant, | |
typename std::remove_reference<typename std::remove_cv<T>::type>::type | |
>::value | |
&& | |
!std::is_same< | |
VarNR, | |
typename std::remove_reference<typename std::remove_cv<T>::type>::type | |
>::value, | |
Variant& | |
>::type operator=(T&& v) { | |
set(std::forward<T>(v)); | |
return *this; | |
} | |
Variant operator + () const = delete; | |
Variant &operator += (const Variant& v) = delete; | |
Variant &operator += (int n) = delete; | |
Variant &operator += (int64_t n) = delete; | |
Variant &operator += (double n) = delete; | |
Variant operator - () const = delete; | |
Variant operator - (const Variant& v) const = delete; | |
Variant &operator -= (const Variant& v) = delete; | |
Variant &operator -= (int n) = delete; | |
Variant &operator -= (int64_t n) = delete; | |
Variant &operator -= (double n) = delete; | |
Variant operator * (const Variant& v) const = delete; | |
Variant &operator *= (const Variant& v) = delete; | |
Variant &operator *= (int n) = delete; | |
Variant &operator *= (int64_t n) = delete; | |
Variant &operator *= (double n) = delete; | |
Variant operator / (const Variant& v) const = delete; | |
Variant &operator /= (const Variant& v) = delete; | |
Variant &operator /= (int n) = delete; | |
Variant &operator /= (int64_t n) = delete; | |
Variant &operator /= (double n) = delete; | |
int64_t operator % (const Variant& v) const = delete; | |
Variant &operator %= (const Variant& v) = delete; | |
Variant &operator %= (int n) = delete; | |
Variant &operator %= (int64_t n) = delete; | |
Variant &operator %= (double n) = delete; | |
Variant operator | (const Variant& v) const = delete; | |
Variant &operator |= (const Variant& v) = delete; | |
Variant operator & (const Variant& v) const = delete; | |
Variant &operator &= (const Variant& v) = delete; | |
Variant operator ^ (const Variant& v) const = delete; | |
Variant &operator ^= (const Variant& v) = delete; | |
Variant &operator <<=(int64_t n) = delete; | |
Variant &operator >>=(int64_t n) = delete; | |
Variant &operator ++ () = delete; | |
Variant operator ++ (int) = delete; | |
Variant &operator -- () = delete; | |
Variant operator -- (int) = delete; | |
/** | |
* Explicit type conversions | |
*/ | |
bool toBoolean() const { | |
if (isNullType(m_type)) return false; | |
if (hasNumData(m_type)) return m_data.num; | |
return toBooleanHelper(); | |
} | |
char toByte() const { return (char)toInt64();} | |
short toInt16(int base = 10) const { return (short)toInt64(base);} | |
int toInt32(int base = 10) const { return (int)toInt64(base);} | |
int64_t toInt64() const { | |
if (isNullType(m_type)) return 0; | |
if (hasNumData(m_type)) return m_data.num; | |
return toInt64Helper(10); | |
} | |
int64_t toInt64(int base) const { | |
if (isNullType(m_type)) return 0; | |
if (hasNumData(m_type)) return m_data.num; | |
return toInt64Helper(base); | |
} | |
double toDouble() const { | |
if (m_type == KindOfDouble) return m_data.dbl; | |
return tvCastToDouble(*asTypedValue()); | |
} | |
String toString(ConvNoticeLevel level = ConvNoticeLevel::None, | |
const StringData* notice_reason = nullptr) const& { | |
if (isStringType(m_type)) return String{m_data.pstr}; | |
return String::attach(tvCastToStringData(*this, level, notice_reason)); | |
} | |
String toString(ConvNoticeLevel level = ConvNoticeLevel::None, | |
const StringData* notice_reason = nullptr) && { | |
if (isStringType(m_type)) { | |
m_type = KindOfNull; | |
return String::attach(m_data.pstr); | |
} | |
return toString(level, notice_reason); | |
} | |
// Convert a non-array-like type to a PHP array, leaving PHP arrays and Hack | |
// arrays unchanged. Use toPHPArray() if you want the result to always be a | |
// PHP array. | |
template <IntishCast IC = IntishCast::None> | |
Array toArray() const { | |
if (isArrayLikeType(m_type)) return Array{m_data.parr}; | |
return Array::attach(tvCastToArrayLikeData<IC>(*this)); | |
} | |
Array toPHPArray() const { | |
if (isArrayType(m_type)) return Array(m_data.parr); | |
return toPHPArrayHelper(); | |
} | |
Object toObject() const { | |
if (isObjectType(m_type)) return Object{m_data.pobj}; | |
return Object::attach(tvCastToObjectData(*this)); | |
} | |
Resource toResource() const { | |
if (m_type == KindOfResource) return Resource{m_data.pres}; | |
return toResourceHelper(); | |
} | |
Array toVec() const { | |
if (isVecType(m_type)) return Array{m_data.parr}; | |
auto copy = *this; | |
tvCastToVecInPlace(copy.asTypedValue()); | |
assertx(copy.isVec()); | |
return Array::attach(copy.detach().m_data.parr); | |
} | |
Array toDict() const { | |
if (isDictType(m_type)) return Array{m_data.parr}; | |
auto copy = *this; | |
tvCastToDictInPlace(copy.asTypedValue()); | |
assertx(copy.isDict()); | |
return Array::attach(copy.detach().m_data.parr); | |
} | |
Array toKeyset() const { | |
if (isKeysetType(m_type)) return Array{m_data.parr}; | |
auto copy = *this; | |
tvCastToKeysetInPlace(copy.asTypedValue()); | |
assertx(copy.isKeyset()); | |
return Array::attach(copy.detach().m_data.parr); | |
} | |
Array toVArray() const { | |
if (RuntimeOption::EvalHackArrDVArrs) { | |
auto a = toVec(); | |
a.setLegacyArray(RuntimeOption::EvalHackArrDVArrMark); | |
return a; | |
} | |
if (isArrayLikeType(m_type)) return asCArrRef().toVArray(); | |
auto copy = *this; | |
tvCastToVArrayInPlace(copy.asTypedValue()); | |
assertx(copy.isPHPArray() && copy.asCArrRef().isVArray()); | |
return Array::attach(copy.detach().m_data.parr); | |
} | |
Array toDArray() const { | |
if (RuntimeOption::EvalHackArrDVArrs) { | |
auto a = toDict(); | |
a.setLegacyArray(RuntimeOption::EvalHackArrDVArrMark); | |
return a; | |
} | |
if (isArrayLikeType(m_type)) return asCArrRef().toDArray(); | |
auto copy = *this; | |
tvCastToDArrayInPlace(copy.asTypedValue()); | |
assertx(copy.isPHPArray() && copy.asCArrRef().isDArray()); | |
return Array::attach(copy.detach().m_data.parr); | |
} | |
template <typename T> | |
typename std::enable_if<std::is_base_of<ResourceData,T>::value, bool>::type | |
isa() const { | |
if (m_type == KindOfResource) { | |
return m_data.pres->data()->instanceof<T>(); | |
} | |
return false; | |
} | |
template <typename T> | |
typename std::enable_if<std::is_base_of<ObjectData,T>::value, bool>::type | |
isa() const { | |
if (m_type == KindOfObject) { | |
return m_data.pobj->instanceof<T>(); | |
} | |
return false; | |
} | |
/* | |
* Convert to a valid key or throw an exception. If convertStrKeys is true | |
* int-like string keys will be converted to int keys. | |
*/ | |
template <IntishCast IC = IntishCast::None> | |
VarNR toKey(const ArrayData*) const; | |
/* Creating a temporary Array, String, or Object with no ref-counting and | |
* no type checking, use it only when we have checked the variant type and | |
* we are sure the internal data will have a reference until the temporary | |
* one gets out-of-scope. | |
*/ | |
StrNR toStrNR() const { | |
return StrNR(getStringData()); | |
} | |
ArrNR toArrNR() const { | |
return ArrNR(getArrayData()); | |
} | |
ObjNR toObjNR() const { | |
return ObjNR(getObjectData()); | |
} | |
auto toFuncVal() const { | |
return const_variant_ref{*this}.toFuncVal(); | |
} | |
auto toClassVal() const { | |
return const_variant_ref{*this}.toClassVal(); | |
} | |
auto toLazyClassVal() const { | |
return const_variant_ref{*this}.toLazyClassVal(); | |
} | |
ClsMethDataRef toClsMethVal() const { | |
return const_variant_ref{*this}.toClsMethVal(); | |
} | |
/* | |
* Low level access that should be restricted to internal use. | |
*/ | |
int64_t *getInt64Data() const { | |
assertx(getType() == KindOfInt64); | |
return const_cast<int64_t*>(&m_data.num); | |
} | |
double *getDoubleData() const { | |
assertx(getType() == KindOfDouble); | |
return const_cast<double*>(&m_data.dbl); | |
} | |
StringData *getStringData() const { | |
assertx(isStringType(getType())); | |
return m_data.pstr; | |
} | |
StringData *getStringDataOrNull() const { | |
// This is a necessary evil because getStringData() returns | |
// an undefined result if this is a null variant | |
assertx(isNull() || isString()); | |
return isNullType(m_type) ? nullptr : m_data.pstr; | |
} | |
ArrayData *getArrayData() const { | |
assertx(isArray()); | |
return m_data.parr; | |
} | |
ArrayData *getArrayDataOrNull() const { | |
// This is a necessary evil because getArrayData() returns | |
// an undefined result if this is a null variant | |
assertx(isNull() || isArray()); | |
return isNullType(m_type) ? nullptr : m_data.parr; | |
} | |
ObjectData* getObjectData() const { | |
assertx(is(KindOfObject)); | |
return m_data.pobj; | |
} | |
ObjectData *getObjectDataOrNull() const { | |
// This is a necessary evil because getObjectData() returns | |
// an undefined result if this is a null variant | |
assertx(isNull() || is(KindOfObject)); | |
return isNullType(m_type) ? nullptr : m_data.pobj; | |
} | |
int64_t getNumData() const { return m_data.num; } | |
/* | |
* Make any Variant strings and arrays not ref counted (e.g., static). | |
* Use it, for example, if you need a long-lived Variant before the Memory | |
* Manager has been initialized. | |
* You will still get an assertion if the Variant is an object, resource, etc. | |
*/ | |
void setEvalScalar(); | |
/* | |
* Access this Variant as a TypedValue. | |
*/ | |
const TypedValue* asTypedValue() const { return this; } | |
TypedValue* asTypedValue() { return this; } | |
/* | |
* Read this Variant as an InitCell, without incrementing the | |
* reference count. I.e. turn KindOfUninit into KindOfNull. | |
*/ | |
TypedValue asInitTVTmp() const { | |
if (m_type == KindOfUninit) return make_tv<KindOfNull>(); | |
return *this; | |
} | |
TypedValue detach() noexcept { | |
auto tv = *asTypedValue(); | |
m_type = KindOfNull; | |
return tv; | |
} | |
private: | |
ResourceData* getResourceData() const { | |
assertx(is(KindOfResource)); | |
return m_data.pres->data(); | |
} | |
ResourceData* detachResourceData() { | |
assertx(is(KindOfResource)); | |
assertx(m_type == KindOfResource); | |
m_type = KindOfNull; | |
return m_data.pres->data(); | |
} | |
ObjectData* detachObjectData() { | |
assertx(is(KindOfObject)); | |
assertx(m_type == KindOfObject); | |
m_type = KindOfNull; | |
return m_data.pobj; | |
} | |
template <typename T> | |
friend typename std::enable_if< | |
std::is_base_of<ResourceData,T>::value, | |
ResourceData* | |
>::type deref(const Variant& v) { return v.getResourceData(); } | |
template <typename T> | |
friend typename std::enable_if< | |
std::is_base_of<ObjectData,T>::value, | |
ObjectData* | |
>::type deref(const Variant& v) { return v.getObjectData(); } | |
template <typename T> | |
friend typename std::enable_if< | |
std::is_base_of<ResourceData,T>::value, | |
ResourceData* | |
>::type detach(Variant&& v) { return v.detachResourceData(); } | |
template <typename T> | |
friend typename std::enable_if< | |
std::is_base_of<ObjectData,T>::value, | |
ObjectData* | |
>::type detach(Variant&& v) { return v.detachObjectData(); } | |
explicit Variant(ResourceData* v) noexcept { | |
if (v) { | |
m_type = KindOfResource; | |
m_data.pres = v->hdr(); | |
v->incRefCount(); | |
} else { | |
m_type = KindOfNull; | |
} | |
} | |
explicit Variant(ResourceHdr* v) noexcept { | |
if (v) { | |
m_type = KindOfResource; | |
m_data.pres = v; | |
v->incRefCount(); | |
} else { | |
m_type = KindOfNull; | |
} | |
} | |
/* | |
* This set of constructors act like the normal constructors for the | |
* given types except that they do not increment the reference count | |
* of the passed value. They are used for the req::ptr move constructor. | |
*/ | |
Variant(StringData* var, Attach) noexcept { | |
if (var) { | |
m_type = var->isRefCounted() ? KindOfString : KindOfPersistentString; | |
m_data.pstr = var; | |
} else { | |
m_type = KindOfNull; | |
} | |
} | |
Variant(ArrayData* var, Attach) noexcept { | |
if (var) { | |
m_type = | |
var->isRefCounted() ? var->toDataType() : var->toPersistentDataType(); | |
m_data.parr = var; | |
} else { | |
m_type = KindOfNull; | |
} | |
} | |
Variant(ObjectData* var, Attach) noexcept { | |
if (var) { | |
m_type = KindOfObject; | |
m_data.pobj = var; | |
} else { | |
m_type = KindOfNull; | |
} | |
} | |
Variant(ResourceData* var, Attach) noexcept { | |
if (var) { | |
m_type = KindOfResource; | |
m_data.pres = var->hdr(); | |
} else { | |
m_type = KindOfNull; | |
} | |
} | |
Variant(ResourceHdr* var, Attach) noexcept { | |
if (var) { | |
m_type = KindOfResource; | |
m_data.pres = var; | |
} else { | |
m_type = KindOfNull; | |
} | |
} | |
Variant(TypedValue tv, Attach) noexcept : TypedValue(tv) {} | |
Variant(TypedValue tv, Wrap) noexcept : TypedValue(tv) { | |
tvIncRefGen(*asTypedValue()); | |
} | |
bool isPrimitive() const { return !isRefcountedType(m_type); } | |
bool isObjectConvertable() { | |
return isNullType(m_type) || | |
(m_type == KindOfBoolean && !m_data.num) || | |
(isStringType(m_type) && m_data.pstr->empty()); | |
} | |
void set(bool v) noexcept; | |
void set(int v) noexcept; | |
void set(int64_t v) noexcept; | |
void set(double v) noexcept; | |
void set(const char* v) = delete; | |
void set(const std::string & v) { | |
return set(String(v)); | |
} | |
void set(StringData *v) noexcept; | |
void set(ArrayData *v) noexcept; | |
void set(ObjectData *v) noexcept; | |
void set(ResourceHdr *v) noexcept; | |
void set(ResourceData *v) noexcept { set(v->hdr()); } | |
void set(const StringData *v) = delete; | |
void set(const ArrayData *v) = delete; | |
void set(const ObjectData *v) = delete; | |
void set(const ResourceData *v) = delete; | |
void set(const ResourceHdr *v) = delete; | |
void set(const String& v) noexcept { set(v.get()); } | |
void set(const StaticString & v) noexcept; | |
void set(const Array& v) noexcept { set(v.get()); } | |
void set(const Object& v) noexcept { set(v.get()); } | |
void set(const Resource& v) noexcept { set(v.hdr()); } | |
void set(String&& v) noexcept { steal(v.detach()); } | |
void set(Array&& v) noexcept { steal(v.detach()); } | |
void set(Object&& v) noexcept { steal(v.detach()); } | |
void set(Resource&& v) noexcept { steal(v.detachHdr()); } | |
template<typename T> | |
void set(const req::ptr<T> &v) noexcept { | |
return set(v.get()); | |
} | |
template <typename T> | |
void set(req::ptr<T>&& v) noexcept { | |
return steal(v.detach()); | |
} | |
void steal(StringData* v) noexcept; | |
void steal(ArrayData* v) noexcept; | |
void steal(ObjectData* v) noexcept; | |
void steal(ResourceHdr* v) noexcept; | |
void steal(ResourceData* v) noexcept { steal(v->hdr()); } | |
private: | |
bool toBooleanHelper() const; | |
int64_t toInt64Helper(int base = 10) const; | |
Array toPHPArrayHelper() const; | |
Resource toResourceHelper() const; | |
DataType convertToNumeric(int64_t *lval, double *dval) const; | |
}; | |
Variant operator+(const Variant & lhs, const Variant & rhs) = delete; | |
/////////////////////////////////////////////////////////////////////////////// | |
/* | |
* Definitions for some members of variant_ref et al. that use Variant. | |
*/ | |
inline variant_ref::variant_ref(Variant& v) | |
: variant_ref_detail::base<false>{v.asTypedValue()} | |
{} | |
inline const_variant_ref::const_variant_ref(const Variant& v) | |
: variant_ref_detail::base<true>{v.asTypedValue()} | |
{} | |
inline variant_ref& variant_ref::operator=(const Variant& v) noexcept { | |
return assign(v); | |
} | |
inline variant_ref& variant_ref::assign(const Variant& v) noexcept { | |
tvSet(tvToInit(*v.asTypedValue()), m_val); | |
return *this; | |
} | |
inline variant_ref& variant_ref::operator=(Variant &&rhs) noexcept { | |
variant_ref lhs = *this; | |
Variant goner((Variant::NoInit())); | |
goner.m_data = val(lhs.m_val); | |
goner.m_type = type(lhs.m_val); | |
if (rhs.m_type == KindOfUninit) { | |
type(lhs.m_val) = KindOfNull; | |
} else { | |
type(lhs.m_val) = rhs.m_type; | |
val(lhs.m_val) = rhs.m_data; | |
rhs.m_type = KindOfNull; | |
} | |
return *this; | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
// VarNR | |
struct VarNR : private TypedValue { | |
static VarNR MakeKey(const String& s) { | |
if (s.empty()) return VarNR(staticEmptyString()); | |
return VarNR(s); | |
} | |
// Use to hold variant that do not need ref-counting | |
explicit VarNR(bool v) { m_type = KindOfBoolean; m_data.num = (v?1:0);} | |
explicit VarNR(int v) { m_type = KindOfInt64; m_data.num = v;} | |
// The following two overloads will accept int64_t whether it's | |
// implemented as long or long long. | |
explicit VarNR(long v) { m_type = KindOfInt64; m_data.num = v;} | |
explicit VarNR(long long v) { m_type = KindOfInt64; m_data.num = v;} | |
explicit VarNR(uint64_t v) { m_type = KindOfInt64; m_data.num = v;} | |
explicit VarNR(double v) { m_type = KindOfDouble; m_data.dbl = v;} | |
explicit VarNR(const StaticString &v) { | |
assertx(v.get() && !v.get()->isRefCounted()); | |
m_type = KindOfPersistentString; | |
m_data.pstr = v.get(); | |
} | |
explicit VarNR(const String& v) : VarNR(v.get()) {} | |
explicit VarNR(const Array& v) : VarNR(v.get()) {} | |
explicit VarNR(const Object& v) : VarNR(v.get()) {} | |
explicit VarNR(StringData *v); | |
explicit VarNR(const StringData *v) { | |
assertx(v && !v->isRefCounted()); | |
m_type = KindOfPersistentString; | |
m_data.pstr = const_cast<StringData*>(v); | |
} | |
explicit VarNR(ArrayData *v); | |
explicit VarNR(const ArrayData*) = delete; | |
explicit VarNR(ObjectData *v); | |
explicit VarNR(const ObjectData*) = delete; | |
explicit VarNR(TypedValue tv) { m_type = tv.m_type; m_data = tv.m_data; } | |
explicit VarNR(const Variant& v) : VarNR{*v.asTypedValue()} {} | |
VarNR(const VarNR&) = delete; | |
VarNR& operator=(const VarNR&) = delete; | |
explicit VarNR() { asVariant()->asTypedValue()->m_type = KindOfUninit; } | |
~VarNR() { | |
if (debug) { | |
checkRefCount(); | |
memset(this, kTVTrashFill2, sizeof(*this)); | |
} | |
} | |
TypedValue tv() const { return *this; } | |
operator const Variant&() const { return *asVariant(); } | |
bool isNull() const { | |
return asVariant()->isNull(); | |
} | |
private: | |
/* implicit */ VarNR(const char* v) = delete; | |
/* implicit */ VarNR(const std::string & v) = delete; | |
const Variant *asVariant() const { | |
return &tvAsCVarRef(static_cast<const TypedValue*>(this)); | |
} | |
Variant* asVariant() { | |
return &tvAsVariant(static_cast<TypedValue*>(this)); | |
} | |
void checkRefCount() { | |
assertx(isRealType(m_type)); | |
switch (m_type) { | |
DT_UNCOUNTED_CASE: | |
return; | |
case KindOfString: | |
assertx(m_data.pstr->checkCount()); | |
return; | |
case KindOfVec: | |
case KindOfDict: | |
case KindOfKeyset: | |
case KindOfDArray: | |
case KindOfVArray: | |
assertx(m_data.parr->checkCount()); | |
return; | |
case KindOfClsMeth: | |
assertx(checkCountClsMeth(m_data.pclsmeth)); | |
return; | |
case KindOfRClsMeth: | |
assertx(m_data.prclsmeth->checkCount()); | |
case KindOfObject: | |
assertx(m_data.pobj->checkCount()); | |
return; | |
case KindOfResource: | |
assertx(m_data.pres->checkCount()); | |
return; | |
case KindOfRecord: | |
assertx(m_data.prec->checkCount()); | |
return; | |
case KindOfRFunc: | |
assertx(m_data.prfunc->checkCount()); | |
return; | |
} | |
not_reached(); | |
} | |
}; | |
////////////////////////////////////////////////////////////////////// | |
/* | |
* The lvalBlackHole is used in array operations when a NewElem can't | |
* create a new slot. (Basically if the next integer key in an array | |
* is already at the maximum integer.) | |
*/ | |
Variant& lvalBlackHole(); | |
/* | |
* The lvalBlackHole has request lifetime. | |
*/ | |
void initBlackHole(); | |
void clearBlackHole(); | |
/////////////////////////////////////////////////////////////////////////////// | |
// breaking circular dependencies | |
inline Variant Array::operator[](TypedValue key) const { | |
return Variant::wrap(lookup(key)); | |
} | |
inline Variant Array::operator[](int key) const { | |
return Variant::wrap(lookup(key)); | |
} | |
inline Variant Array::operator[](int64_t key) const { | |
return Variant::wrap(lookup(key)); | |
} | |
inline Variant Array::operator[](const String& key) const { | |
return Variant::wrap(lookup(key)); | |
} | |
inline Variant Array::operator[](const Variant& key) const { | |
return Variant::wrap(lookup(key)); | |
} | |
inline void Array::append(const Variant& v) { | |
append(*v.asTypedValue()); | |
} | |
ALWAYS_INLINE Variant uninit_null() { | |
return Variant(); | |
} | |
ALWAYS_INLINE Variant init_null() { | |
return Variant(Variant::NullInit()); | |
} | |
inline void concat_assign(Variant &v1, const char* s2) = delete; | |
inline void concat_assign(tv_lval lhs, const String& s2) { | |
if (!isStringType(type(lhs))) tvCastToStringInPlace(lhs); | |
asStrRef(lhs) += s2; | |
} | |
////////////////////////////////////////////////////////////////////// | |
inline Array& forceToArray(Variant& var) { | |
if (!var.isArray()) var = Variant(Array::Create()); | |
return var.asArrRef(); | |
} | |
inline Array& forceToArray(tv_lval lval) { | |
if (!isArrayLikeType(lval.type())) { | |
tvMove(make_array_like_tv(ArrayData::Create()), lval); | |
} | |
return asArrRef(lval); | |
} | |
inline Array& forceToDict(Variant& var) { | |
if (!var.isDict()) var = Variant(Array::CreateDict()); | |
return var.asArrRef(); | |
} | |
inline Array& forceToDict(tv_lval lval) { | |
if (!isDictType(lval.type())) { | |
tvSet(make_tv<KindOfDict>(ArrayData::CreateDict()), lval); | |
} | |
return asArrRef(lval); | |
} | |
inline Array& forceToDArray(Variant& var) { | |
if (!tvIsHAMSafeDArray(var.asTypedValue())) { | |
var = Variant(Array::CreateDArray()); | |
} | |
return var.asArrRef(); | |
} | |
inline Array& forceToDArray(tv_lval lval) { | |
if (!tvIsHAMSafeDArray(lval)) { | |
tvMove(make_array_like_tv(ArrayData::CreateDArray()), lval); | |
} | |
return asArrRef(lval); | |
} | |
////////////////////////////////////////////////////////////////////// | |
ALWAYS_INLINE Variant empty_string_variant() { | |
return Variant(staticEmptyString(), Variant::PersistentStrInit{}); | |
} | |
template <typename T> | |
inline Variant toVariant(const req::ptr<T>& p) { | |
return p ? Variant(p) : Variant(false); | |
} | |
template <typename T> | |
inline Variant toVariant(req::ptr<T>&& p) { | |
return p ? Variant(std::move(p)) : Variant(false); | |
} | |
template <> | |
inline bool ptr_is_null(const Variant& v) { | |
return v.isNull(); | |
} | |
template <typename T> | |
inline bool isa_non_null(const Variant& v) { | |
return v.isa<T>(); | |
} | |
// Defined here to avoid introducing a dependency cycle between type-variant | |
// and type-array | |
template <IntishCast IC> | |
ALWAYS_INLINE TypedValue Array::convertKey(TypedValue k) const { | |
return tvToKey<IC>(k, m_arr ? m_arr.get() : ArrayData::Create()); | |
} | |
template <IntishCast IC> | |
ALWAYS_INLINE TypedValue Array::convertKey(const Variant& k) const { | |
return convertKey<IC>(*k.asTypedValue()); | |
} | |
template <IntishCast IC> | |
inline VarNR Variant::toKey(const ArrayData* ad) const { | |
return VarNR(tvToKey<IC>(*this, ad)); | |
} | |
struct alignas(16) OptionalVariant { | |
OptionalVariant() { | |
m_tv.m_type = kInvalidDataType; | |
} | |
~OptionalVariant() { | |
clear(); | |
} | |
OptionalVariant(const OptionalVariant& other) { | |
if (other.hasValue()) { | |
construct(other.value()); | |
return; | |
} | |
m_tv.m_type = kInvalidDataType; | |
} | |
OptionalVariant(OptionalVariant&& other) noexcept { | |
if (other.hasValue()) { | |
construct(std::move(other.value())); | |
other.m_tv.m_type = kInvalidDataType; | |
return; | |
} | |
m_tv.m_type = kInvalidDataType; | |
} | |
template<typename Arg> | |
OptionalVariant& operator=(Arg&& arg) { | |
assign(std::forward<Arg>(arg)); | |
return *this; | |
} | |
OptionalVariant& operator=(const OptionalVariant& arg) { | |
assign(arg); | |
return *this; | |
} | |
OptionalVariant& operator=(OptionalVariant&& arg) { | |
assign(std::move(arg)); | |
return *this; | |
} | |
bool hasValue() const { | |
return m_tv.m_type != kInvalidDataType; | |
} | |
bool has_value() const { | |
return hasValue(); | |
} | |
Variant& value() { | |
assertx(hasValue()); | |
return tvAsVariant(&m_tv); | |
} | |
const Variant& value() const { | |
assertx(hasValue()); | |
return tvAsCVarRef(&m_tv); | |
} | |
void clear() { | |
if (hasValue()) { | |
auto const old = m_tv; | |
m_tv.m_type = kInvalidDataType; | |
tvDecRefGen(old); | |
} | |
} | |
template <class... Args> | |
Variant& emplace(Args&&... args) { | |
clear(); | |
construct(std::forward<Args>(args)...); | |
return value(); | |
} | |
void assign(const Variant& other) { | |
if (hasValue()) { | |
value() = other; | |
return; | |
} | |
construct(other); | |
} | |
void assign(Variant&& other) { | |
if (hasValue()) { | |
value() = std::move(other); | |
return; | |
} | |
construct(std::move(other)); | |
} | |
void assign(const OptionalVariant& other) { | |
if (other.hasValue()) return assign(other.value()); | |
clear(); | |
} | |
void assign(OptionalVariant&& other) { | |
if (other.hasValue()) { | |
assign(std::move(other.value())); | |
other.m_tv.m_type = kInvalidDataType; | |
return; | |
} | |
clear(); | |
} | |
operator bool() const { return hasValue(); } | |
const Variant* operator->() const { return &value(); } | |
Variant* operator->() { return &value(); } | |
const Variant& operator*() const & { return value(); } | |
Variant& operator*() & { return value(); } | |
Variant&& operator*() && { return std::move(value()); } | |
private: | |
template<typename... Args> | |
void construct(Args&&... args) { | |
new (&m_tv) Variant(std::forward<Args>(args)...); | |
} | |
TypedValue m_tv; | |
}; | |
////////////////////////////////////////////////////////////////////// | |
} |