hphp/runtime/vm/member-operations.h (1,972 lines of code) (raw):
/*
+----------------------------------------------------------------------+
| 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 <type_traits>
#include "hphp/runtime/base/array-data-defs.h"
#include "hphp/runtime/base/array-data.h"
#include "hphp/runtime/base/bespoke-array.h"
#include "hphp/runtime/base/builtin-functions.h"
#include "hphp/runtime/base/collections.h"
#include "hphp/runtime/base/datatype.h"
#include "hphp/runtime/base/req-root.h"
#include "hphp/runtime/base/strings.h"
#include "hphp/runtime/base/tv-conversions.h"
#include "hphp/runtime/base/tv-refcount.h"
#include "hphp/runtime/base/tv-type.h"
#include "hphp/runtime/base/type-array.h"
#include "hphp/runtime/base/type-string.h"
#include "hphp/runtime/base/vanilla-dict.h"
#include "hphp/runtime/base/vanilla-keyset.h"
#include "hphp/runtime/base/vanilla-vec.h"
#include "hphp/runtime/vm/runtime.h"
#include "hphp/runtime/vm/type-constraint.h"
#include "hphp/system/systemlib.h"
#include <folly/tracing/StaticTracepoint.h>
namespace HPHP {
struct InvalidSetMException : std::runtime_error {
InvalidSetMException()
: std::runtime_error("Empty InvalidSetMException")
, m_tv(make_tv<KindOfNull>())
{}
explicit InvalidSetMException(const TypedValue& value)
: std::runtime_error(folly::format("InvalidSetMException containing {}",
value.pretty()).str())
, m_tv(value)
{}
~InvalidSetMException() noexcept override {}
const TypedValue& tv() const { return m_tv; };
private:
/* m_tv will contain a TypedValue with a reference destined for the
* VM eval stack. */
req::root<TypedValue> m_tv;
};
/*
* KeyType and the associated functions below are used to generate member
* operation functions specialized for certain key types. Many functions take a
* KeyType template parameter, then use key_type<keyType> as the type of their
* key parameter. Depending on which KeyType is used, the parameter will be a
* TypedValue, int64_t, or StringData*.
*/
enum class KeyType {
Any, // Key is passed as a TypedValue and could be any type
Int, // Key is passed as an int64_t
Str, // Key is passed as a StringData*
};
/* KeyTypeTraits maps from KeyType to the C++ type holding they key. */
template<KeyType> struct KeyTypeTraits;
template<> struct KeyTypeTraits<KeyType::Any> { typedef TypedValue type; };
template<> struct KeyTypeTraits<KeyType::Int> { typedef int64_t type; };
template<> struct KeyTypeTraits<KeyType::Str> { typedef StringData* type; };
/* key_type is the type used in the signatures of functions taking a member
* key. */
template<KeyType kt> using key_type = typename KeyTypeTraits<kt>::type;
/* initScratchKey is used in scenarios where we want a TypedValue key
* regardless of what the current function was given. */
inline TypedValue initScratchKey(TypedValue tv) {
return tv;
}
inline TypedValue initScratchKey(int64_t key) {
return make_tv<KindOfInt64>(key);
}
inline TypedValue initScratchKey(StringData* key) {
return make_tv<KindOfString>(key);
}
/* keyAsValue transforms a key into a value suitable for indexing into an
* Array. */
inline const Variant& keyAsValue(TypedValue& key) {
return tvAsCVarRef(&key);
}
inline int64_t keyAsValue(int64_t key) { return key; }
inline StrNR keyAsValue(StringData* key) { return StrNR(key); }
/* prepareKey is used by operations that need to cast their key to a
* string. For generic keys, the returned value must be decreffed after use. */
StringData* prepareAnyKey(TypedValue* tv);
inline StringData* prepareKey(TypedValue tv) { return prepareAnyKey(&tv); }
inline StringData* prepareKey(StringData* sd) { return sd; }
/* releaseKey helps with decreffing a StringData* returned from
* prepareKey. When used with KeyType::Any, corresponding to
* prepareKey(TypedValue), it will consume the reference produced by
* prepareKey. When used with KeyType::Str, corresponding to
* prepareKey(StringData*), it is a nop. */
template<KeyType keyType>
inline void releaseKey(StringData* sd) {
static_assert(keyType == KeyType::Any, "bad KeyType");
decRefStr(sd);
}
template<>
inline void releaseKey<KeyType::Str>(StringData*) {
// do nothing. we don't own a reference to this string.
}
inline void failOnNonCollectionObjArrayAccess(ObjectData* obj) {
if (UNLIKELY(!obj->isCollection())) {
raise_error("Cannot use array access on an object");
}
}
[[noreturn]] void throw_cannot_use_newelem_for_lval_read_col();
[[noreturn]] void throw_cannot_use_newelem_for_lval_read(const ArrayData*);
[[noreturn]] void throw_cannot_use_newelem_for_lval_read_clsmeth();
[[noreturn]] void throw_cannot_unset_for_clsmeth();
[[noreturn]] void unknownBaseType(DataType);
namespace detail {
[[noreturn]]
inline void raiseFalseyPromotion(tv_rval base) {
if (tvIsNull(base)) {
throwFalseyPromoteException("null");
} else if (tvIsBool(base)) {
throwFalseyPromoteException("false");
} else if (tvIsString(base)) {
throwFalseyPromoteException("empty string");
}
always_assert(false);
}
[[noreturn]]
inline void raiseEmptyObject() {
if (RuntimeOption::PHP7_EngineExceptions) {
SystemLib::throwErrorObject(Strings::SET_PROP_NON_OBJECT);
} else {
SystemLib::throwExceptionObject(Strings::SET_PROP_NON_OBJECT);
}
}
}
/**
* Elem when base is Null
*/
inline TypedValue ElemEmptyish() {
return make_tv<KindOfNull>();
}
/**
* Elem when base is a Vec
*/
template<MOpMode mode>
inline TypedValue ElemVecPre(ArrayData* base, int64_t key) {
return VanillaVec::NvGetInt(base, key);
}
template<MOpMode mode>
inline TypedValue ElemVecPre(ArrayData* base, StringData* key) {
if (mode == MOpMode::Warn || mode == MOpMode::InOut) {
throwInvalidArrayKeyException(key, base);
}
return make_tv<KindOfUninit>();
}
template<MOpMode mode>
inline TypedValue ElemVecPre(ArrayData* base, TypedValue key) {
auto const dt = key.m_type;
if (isIntType(dt)) return ElemVecPre<mode>(base, key.m_data.num);
if (isStringType(dt)) return ElemVecPre<mode>(base, key.m_data.pstr);
throwInvalidArrayKeyException(&key, base);
}
template<MOpMode mode, KeyType keyType>
inline TypedValue ElemVec(ArrayData* base, key_type<keyType> key) {
assertx(base->isVanillaVec());
auto const result = ElemVecPre<mode>(base, key);
if (UNLIKELY(!result.is_init())) {
if (mode != MOpMode::Warn && mode != MOpMode::InOut) return ElemEmptyish();
throwOOBArrayKeyException(key, base);
}
assertx(result.type() != KindOfUninit);
return result;
}
/**
* Elem when base is a Dict
*/
inline TypedValue ElemDictPre(ArrayData* base, int64_t key) {
return VanillaDict::NvGetInt(base, key);
}
inline TypedValue ElemDictPre(ArrayData* base, StringData* key) {
return VanillaDict::NvGetStr(base, key);
}
inline TypedValue ElemDictPre(ArrayData* base, TypedValue key) {
auto const dt = key.m_type;
if (isIntType(dt)) return ElemDictPre(base, key.m_data.num);
if (isStringType(dt)) return ElemDictPre(base, key.m_data.pstr);
throwInvalidArrayKeyException(&key, base);
}
// This helper may also be used when we know we have a VanillaDict in the JIT.
template<MOpMode mode, KeyType keyType>
inline TypedValue ElemDict(ArrayData* base, key_type<keyType> key) {
assertx(base->isVanillaDict());
auto const result = ElemDictPre(base, key);
if (UNLIKELY(!result.is_init())) {
if (mode != MOpMode::Warn && mode != MOpMode::InOut) return ElemEmptyish();
throwOOBArrayKeyException(key, base);
}
assertx(result.type() != KindOfUninit);
return result;
}
/**
* Elem when base is a Keyset
*/
inline TypedValue ElemKeysetPre(ArrayData* base, int64_t key) {
return VanillaKeyset::NvGetInt(base, key);
}
inline TypedValue ElemKeysetPre(ArrayData* base, StringData* key) {
return VanillaKeyset::NvGetStr(base, key);
}
inline TypedValue ElemKeysetPre(ArrayData* base, TypedValue key) {
auto const dt = key.m_type;
if (isIntType(dt)) return ElemKeysetPre(base, key.m_data.num);
if (isStringType(dt)) return ElemKeysetPre(base, key.m_data.pstr);
throwInvalidArrayKeyException(&key, base);
}
template<MOpMode mode, KeyType keyType>
inline TypedValue ElemKeyset(ArrayData* base, key_type<keyType> key) {
assertx(base->isVanillaKeyset());
auto result = ElemKeysetPre(base, key);
if (UNLIKELY(!result.is_init())) {
if (mode != MOpMode::Warn && mode != MOpMode::InOut) return ElemEmptyish();
throwOOBArrayKeyException(key, base);
}
assertx(isIntType(result.type()) || isStringType(result.type()));
return result;
}
/**
* Elem when base is a bespoke Hack array
*/
template<MOpMode mode>
inline TypedValue ElemBespokePre(ArrayData* base, int64_t key) {
return BespokeArray::NvGetInt(base, key);
}
template<MOpMode mode>
inline TypedValue ElemBespokePre(ArrayData* base, StringData* key) {
if ((mode == MOpMode::Warn || mode == MOpMode::InOut) && base->isVecType()) {
throwInvalidArrayKeyException(key, base);
}
return BespokeArray::NvGetStr(base, key);
}
template<MOpMode mode>
inline TypedValue ElemBespokePre(ArrayData* base, TypedValue key) {
auto const dt = key.m_type;
if (isIntType(dt)) return ElemBespokePre<mode>(base, key.m_data.num);
if (isStringType(dt)) return ElemBespokePre<mode>(base, key.m_data.pstr);
throwInvalidArrayKeyException(&key, base);
}
template<MOpMode mode, KeyType keyType>
inline TypedValue ElemBespoke(ArrayData* base, key_type<keyType> key) {
assertx(!base->isVanilla());
auto const result = ElemBespokePre<mode>(base, key);
if (UNLIKELY(!result.is_init())) {
if (mode != MOpMode::Warn && mode != MOpMode::InOut) return ElemEmptyish();
throwOOBArrayKeyException(key, base);
}
return result;
}
/**
* Elem when base is a ClsMeth
*/
template<MOpMode mode>
inline TypedValue ElemClsMethPre(ClsMethDataRef base, int64_t key) {
if (key == 0) {
return make_tv<KindOfString>(
const_cast<StringData*>(base->getCls()->name()));
} else if (key == 1) {
return make_tv<KindOfString>(
const_cast<StringData*>(base->getFunc()->name()));
}
if (mode == MOpMode::Warn || mode == MOpMode::InOut) {
SystemLib::throwOutOfBoundsExceptionObject(
folly::sformat("Out of bounds clsmeth access: invalid index {}", key));
}
return make_tv<KindOfNull>();
}
template<MOpMode mode>
inline TypedValue ElemClsMethPre(ClsMethDataRef base, StringData* key) {
if (mode == MOpMode::Warn || mode == MOpMode::InOut) {
SystemLib::throwInvalidArgumentExceptionObject(
"Invalid clsmeth key: expected a key of type int, string given");
}
return make_tv<KindOfNull>();
}
template<MOpMode mode>
inline TypedValue ElemClsMethPre(ClsMethDataRef base, TypedValue key) {
if (LIKELY(isIntType(type(key)))) {
return ElemClsMethPre<mode>(base, val(key).num);
}
if (mode == MOpMode::Warn || mode == MOpMode::InOut) {
SystemLib::throwInvalidArgumentExceptionObject(
"Invalid clsmeth key: expected a key of type int");
}
return make_tv<KindOfNull>();
}
template<MOpMode mode, KeyType keyType>
inline TypedValue ElemClsMeth(ClsMethDataRef base, key_type<keyType> key) {
return ElemClsMethPre<mode>(base, key);
}
/**
* Elem when base is an Int64, Double, or Resource.
*/
inline TypedValue ElemScalar() {
raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY);
return ElemEmptyish();
}
/**
* Elem when base is a Boolean
*/
inline TypedValue ElemBoolean(TypedValue base) {
return val(base).num ? ElemScalar() : ElemEmptyish();
}
inline int64_t ElemStringPre(int64_t key) {
return key;
}
inline int64_t ElemStringPre(StringData* key) {
return key->toInt64(10);
}
inline int64_t ElemStringPre(TypedValue key) {
if (LIKELY(isIntType(key.m_type))) {
return key.m_data.num;
}
if (LIKELY(isStringType(key.m_type))) {
return key.m_data.pstr->toInt64(10);
}
SystemLib::throwInvalidArgumentExceptionObject(
folly::sformat(
"Invalid string key: expected a key of type int or string, {} given",
describe_actual_type(&key)
)
);
}
/**
* Elem when base is a String
*/
template<MOpMode mode, KeyType keyType>
inline TypedValue ElemString(const StringData* base, key_type<keyType> key) {
auto const offset = ElemStringPre(key);
if (size_t(offset) >= base->size()) {
if (mode == MOpMode::Warn) {
raise_notice("Uninitialized string offset: %" PRId64, offset);
}
return make_tv<KindOfPersistentString>(staticEmptyString());
} else {
auto const sd = base->getChar(offset);
assertx(sd->isStatic());
return make_tv<KindOfPersistentString>(sd);
}
}
/**
* Elem when base is an Object
*/
template<MOpMode mode, KeyType keyType>
inline TypedValue ElemObject(ObjectData* base, key_type<keyType> key) {
failOnNonCollectionObjArrayAccess(base);
auto scratch = initScratchKey(key);
if (mode == MOpMode::Warn) return *collections::at(base, &scratch);
auto const result = collections::get(base, &scratch);
return result ? *result : make_tv<KindOfNull>();
}
/**
* $result = $base[$key];
*/
template<MOpMode mode, KeyType keyType>
NEVER_INLINE TypedValue ElemSlow(TypedValue base, key_type<keyType> key) {
assertx(tvIsPlausible(base));
switch (base.type()) {
case KindOfUninit:
case KindOfNull:
return ElemEmptyish();
case KindOfBoolean:
return ElemBoolean(base);
case KindOfInt64:
case KindOfDouble:
case KindOfResource:
case KindOfRFunc:
case KindOfRClsMeth:
case KindOfFunc:
return ElemScalar();
case KindOfClass:
return ElemString<mode, keyType>(
classToStringHelper(val(base).pclass), key
);
case KindOfLazyClass:
return ElemString<mode, keyType>(
lazyClassToStringHelper(val(base).plazyclass), key
);
case KindOfPersistentString:
case KindOfString:
return ElemString<mode, keyType>(val(base).pstr, key);
// These types are handled in Elem.
case KindOfPersistentVec:
case KindOfVec:
case KindOfPersistentDict:
case KindOfDict:
case KindOfPersistentKeyset:
case KindOfKeyset:
always_assert(false);
case KindOfObject:
return ElemObject<mode, keyType>(val(base).pobj, key);
case KindOfClsMeth:
return ElemScalar();
}
unknownBaseType(type(base));
}
template<MOpMode mode, KeyType keyType = KeyType::Any>
inline TypedValue Elem(TypedValue base, key_type<keyType> key) {
assertx(mode != MOpMode::Define && mode != MOpMode::Unset);
assertx(tvIsPlausible(base));
if (tvIsArrayLike(base)) {
auto const ad = val(base).parr;
if (!ad->isVanilla()) {
return ElemBespoke<mode, keyType>(ad, key);
} else if (ad->isVanillaVec()) {
return ElemVec<mode, keyType>(ad, key);
} else if (ad->isVanillaDict()) {
return ElemDict<mode, keyType>(ad, key);
} else {
return ElemKeyset<mode, keyType>(ad, key);
}
}
if (mode == MOpMode::InOut) throw_invalid_inout_base();
return ElemSlow<mode, keyType>(base, key);
}
/**
* ElemD when base is a bespoke array-like
*/
inline tv_lval ElemDBespokePre(tv_lval base, int64_t key) {
auto ret = BespokeArray::ElemInt(base, key, true);
assertx(tvIsArrayLike(base));
assertx(base.val().parr->hasExactlyOneRef());
return ret;
}
inline tv_lval ElemDBespokePre(tv_lval base, StringData* key) {
auto ret = BespokeArray::ElemStr(base, key, true);
assertx(tvIsArrayLike(base));
assertx(base.val().parr->hasExactlyOneRef());
return ret;
}
inline tv_lval ElemDBespokePre(tv_lval base, TypedValue key) {
auto const dt = key.m_type;
if (isIntType(dt)) return ElemDBespokePre(base, key.m_data.num);
if (isStringType(dt)) return ElemDBespokePre(base, key.m_data.pstr);
throwInvalidArrayKeyException(&key, base.val().parr);
}
template <KeyType keyType>
inline tv_lval ElemDBespoke(tv_lval base, key_type<keyType> key) {
assertx(tvIsArrayLike(base));
assertx(tvIsPlausible(*base));
assertx(!base.val().parr->isVanilla());
auto const result = ElemDBespokePre(base, key);
assertx(result.type() == dt_modulo_persistence(result.type()));
assertx(tvIsPlausible(*base));
assertx(result.type() != KindOfUninit);
return result;
}
/**
* ElemD when base is a Vec
*/
inline tv_lval ElemDVecPre(tv_lval base, int64_t key) {
auto const oldArr = base.val().parr;
auto const lval = VanillaVec::LvalInt(oldArr, key);
assertx(lval.arr->hasExactlyOneRef());
if (lval.arr != oldArr) {
base.type() = dt_with_rc(base.type());
base.val().parr = lval.arr;
assertx(tvIsPlausible(base.tv()));
decRefArr(oldArr);
}
return lval;
}
inline tv_lval ElemDVecPre(tv_lval base, StringData* key) {
throwInvalidArrayKeyException(key, base.val().parr);
}
inline tv_lval ElemDVecPre(tv_lval base, TypedValue key) {
auto const dt = key.m_type;
if (LIKELY(isIntType(dt))) {
return ElemDVecPre(base, key.m_data.num);
} else if (isStringType(dt)) {
return ElemDVecPre(base, key.m_data.pstr);
}
throwInvalidArrayKeyException(&key, base.val().parr);
}
template <KeyType keyType>
inline tv_lval ElemDVec(tv_lval base, key_type<keyType> key) {
assertx(tvIsVec(base));
assertx(tvIsPlausible(base.tv()));
auto const result = ElemDVecPre(base, key);
assertx(tvIsVec(base));
assertx(tvIsPlausible(base.tv()));
assertx(result.type() != KindOfUninit);
return result;
}
/**
* ElemD when base is a Dict
*/
inline tv_lval ElemDDictPre(tv_lval base, int64_t key) {
auto const oldArr = base.val().parr;
auto const lval = VanillaDict::LvalSilentInt(oldArr, key);
if (UNLIKELY(!lval)) {
assertx(oldArr == lval.arr);
throwOOBArrayKeyException(key, oldArr);
}
assertx(lval.arr->hasExactlyOneRef());
if (lval.arr != oldArr) {
base.type() = dt_with_rc(base.type());
base.val().parr = lval.arr;
assertx(tvIsPlausible(base.tv()));
decRefArr(oldArr);
}
return lval;
}
inline tv_lval ElemDDictPre(tv_lval base, StringData* key) {
auto const oldArr = base.val().parr;
auto const lval = VanillaDict::LvalSilentStr(oldArr, key);
if (UNLIKELY(!lval)) {
assertx(oldArr == lval.arr);
throwOOBArrayKeyException(key, oldArr);
}
assertx(lval.arr->hasExactlyOneRef());
if (lval.arr != oldArr) {
base.type() = dt_with_rc(base.type());
base.val().parr = lval.arr;
assertx(tvIsPlausible(base.tv()));
decRefArr(oldArr);
}
return lval;
}
inline tv_lval ElemDDictPre(tv_lval base, TypedValue key) {
auto const dt = key.m_type;
if (isIntType(dt)) {
return ElemDDictPre(base, key.m_data.num);
} else if (isStringType(dt)) {
return ElemDDictPre(base, key.m_data.pstr);
}
throwInvalidArrayKeyException(&key, base.val().parr);
}
template <KeyType keyType>
inline tv_lval ElemDDict(tv_lval base, key_type<keyType> key) {
assertx(tvIsDict(base));
assertx(tvIsPlausible(base.tv()));
auto result = ElemDDictPre(base, key);
assertx(tvIsDict(base));
assertx(tvIsPlausible(base.tv()));
assertx(result.type() != KindOfUninit);
return result;
}
/**
* ElemD when base is a Keyset
*/
[[noreturn]]
inline tv_lval ElemDKeysetPre(tv_lval /*base*/, int64_t /*key*/) {
throwInvalidKeysetOperation();
}
[[noreturn]]
inline tv_lval ElemDKeysetPre(tv_lval /*base*/, StringData* /*key*/) {
throwInvalidKeysetOperation();
}
[[noreturn]]
inline tv_lval ElemDKeysetPre(tv_lval base, TypedValue key) {
auto const dt = key.m_type;
if (isIntType(dt)) ElemDKeysetPre(base, key.m_data.num);
if (isStringType(dt)) ElemDKeysetPre(base, key.m_data.pstr);
throwInvalidArrayKeyException(&key, base.val().parr);
}
template <KeyType keyType>
[[noreturn]]
inline tv_lval ElemDKeyset(tv_lval base, key_type<keyType> key) {
assertx(tvIsKeyset(base));
assertx(tvIsPlausible(base.tv()));
ElemDKeysetPre(base, key);
}
/**
* ElemD when base is Null
*/
[[noreturn]]
inline tv_lval ElemDEmptyish(tv_lval base) {
detail::raiseFalseyPromotion(base);
not_reached();
}
/**
* ElemD when base is an Int64, Double, Resource, Func, or Class.
* We can use immutable_null_base here because setters on null will throw.
*/
inline tv_lval ElemDScalar() {
raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY);
return const_cast<TypedValue*>(&immutable_null_base);
}
/**
* ElemD when base is a Boolean
*/
inline tv_lval ElemDBoolean(tv_lval base) {
return base.val().num ? ElemDScalar() : ElemDEmptyish(base);
}
/**
* ElemD when base is a String
*/
[[noreturn]]
inline tv_lval ElemDString(tv_lval base) {
if (!base.val().pstr->size()) ElemDEmptyish(base);
raise_error("Operator not supported for strings");
not_reached();
}
/**
* ElemD when base is an Object
*/
template<KeyType keyType>
inline tv_lval ElemDObject(tv_lval base, key_type<keyType> key) {
auto obj = base.val().pobj;
failOnNonCollectionObjArrayAccess(obj);
auto scratchKey = initScratchKey(key);
return collections::atLval(obj, &scratchKey);
}
/*
* Intermediate elem operation for defining member instructions.
*/
template<KeyType keyType = KeyType::Any>
tv_lval ElemD(tv_lval base, key_type<keyType> key) {
assertx(tvIsPlausible(base.tv()));
// ElemD helpers hand out lvals to immutable_null_base in cases where we know
// it won't be updated. Confirm that we never do an illegal update on it.
assertx(type(immutable_null_base) == KindOfNull);
if (tvIsArrayLike(base) && !base.val().parr->isVanilla()) {
return ElemDBespoke<keyType>(base, key);
}
switch (base.type()) {
case KindOfUninit:
case KindOfNull:
return ElemDEmptyish(base);
case KindOfBoolean:
return ElemDBoolean(base);
case KindOfInt64:
case KindOfDouble:
case KindOfResource:
case KindOfRFunc:
case KindOfFunc:
case KindOfRClsMeth:
case KindOfClass:
case KindOfLazyClass:
return ElemDScalar();
case KindOfPersistentString:
case KindOfString:
return ElemDString(base);
case KindOfPersistentKeyset:
case KindOfKeyset:
return ElemDKeyset<keyType>(base, key);
case KindOfPersistentDict:
case KindOfDict:
return ElemDDict<keyType>(base, key);
case KindOfPersistentVec:
case KindOfVec:
return ElemDVec<keyType>(base, key);
case KindOfObject:
return ElemDObject<keyType>(base, key);
case KindOfClsMeth:
return ElemDScalar();
}
unknownBaseType(type(base));
}
/**
* ElemU when base is Null. We can use immutable_null_base here because
* unsets on null will succeed with no further updates.
*/
inline tv_lval ElemUEmptyish() {
return const_cast<TypedValue*>(&immutable_null_base);
}
/**
* ElemU when base is a bespoke array-like
*/
inline tv_lval ElemUBespokePre(tv_lval base, int64_t key) {
if (tvIsKeyset(base)) throwInvalidKeysetOperation();
return BespokeArray::ElemInt(base, key, false);
}
inline tv_lval ElemUBespokePre(tv_lval base, StringData* key) {
if (tvIsKeyset(base)) throwInvalidKeysetOperation();
return BespokeArray::ElemStr(base, key, false);
}
inline tv_lval ElemUBespokePre(tv_lval base, TypedValue key) {
auto const dt = key.m_type;
if (isIntType(dt)) return ElemUBespokePre(base, key.m_data.num);
if (isStringType(dt)) return ElemUBespokePre(base, key.m_data.pstr);
throwInvalidArrayKeyException(&key, base.val().parr);
}
template <KeyType keyType>
inline tv_lval ElemUBespoke(tv_lval base, key_type<keyType> key) {
assertx(tvIsArrayLike(base));
assertx(tvIsPlausible(*base));
assertx(!base.val().parr->isVanilla());
auto const result = ElemUBespokePre(base, key);
assertx(tvIsPlausible(*base));
assertx(result.type() != KindOfUninit);
return result;
}
/**
* ElemU when base is a Vec
*/
inline tv_lval ElemUVecPre(tv_lval base, int64_t key) {
auto const oldArr = val(base).parr;
if (UNLIKELY(!VanillaVec::ExistsInt(oldArr, key))) {
return ElemUEmptyish();
}
auto const newArr = [&]{
if (!oldArr->cowCheck()) return oldArr;
decRefArr(oldArr);
auto const newArr = VanillaVec::Copy(oldArr);
type(base) = dt_with_rc(type(base));
val(base).parr = newArr;
assertx(tvIsPlausible(*base));
return newArr;
}();
return VanillaVec::LvalUncheckedInt(newArr, key);
}
inline tv_lval ElemUVecPre(tv_lval /*base*/, StringData* /*key*/) {
return ElemUEmptyish();
}
inline tv_lval ElemUVecPre(tv_lval base, TypedValue key) {
auto const dt = key.m_type;
if (LIKELY(isIntType(dt))) return ElemUVecPre(base, key.m_data.num);
if (isStringType(dt)) return ElemUVecPre(base, key.m_data.pstr);
throwInvalidArrayKeyException(&key, val(base).parr);
}
template <KeyType keyType>
inline tv_lval ElemUVec(tv_lval base, key_type<keyType> key) {
assertx(tvIsVec(base));
assertx(tvIsPlausible(*base));
auto result = ElemUVecPre(base, key);
assertx(tvIsVec(base));
assertx(tvIsPlausible(*base));
assertx(type(result) != KindOfUninit);
return result;
}
/**
* ElemU when base is a Dict
*/
inline tv_lval ElemUDictPre(tv_lval base, int64_t key) {
ArrayData* oldArr = val(base).parr;
auto const lval = VanillaDict::LvalSilentInt(oldArr, key);
if (UNLIKELY(!lval)) {
return ElemUEmptyish();
}
if (lval.arr != oldArr) {
type(base) = dt_with_rc(type(base));
val(base).parr = lval.arr;
assertx(tvIsPlausible(*base));
decRefArr(oldArr);
}
return lval;
}
inline tv_lval ElemUDictPre(tv_lval base, StringData* key) {
ArrayData* oldArr = val(base).parr;
auto const lval = VanillaDict::LvalSilentStr(oldArr, key);
if (UNLIKELY(!lval)) {
return ElemUEmptyish();
}
if (lval.arr != oldArr) {
type(base) = dt_with_rc(type(base));
val(base).parr = lval.arr;
assertx(tvIsPlausible(*base));
decRefArr(oldArr);
}
return lval;
}
inline tv_lval ElemUDictPre(tv_lval base, TypedValue key) {
auto const dt = key.m_type;
if (isIntType(dt)) return ElemUDictPre(base, key.m_data.num);
if (isStringType(dt)) return ElemUDictPre(base, key.m_data.pstr);
throwInvalidArrayKeyException(&key, val(base).parr);
}
template <KeyType keyType>
inline tv_lval ElemUDict(tv_lval base, key_type<keyType> key) {
assertx(tvIsDict(base));
assertx(tvIsPlausible(*base));
auto result = ElemUDictPre(base, key);
assertx(tvIsDict(base));
assertx(tvIsPlausible(*base));
assertx(type(result) != KindOfUninit);
return result;
}
/**
* ElemU when base is a Keyset
*/
[[noreturn]] inline tv_lval
ElemUKeysetPre(tv_lval /*base*/, int64_t /*key*/) {
throwInvalidKeysetOperation();
}
[[noreturn]] inline tv_lval
ElemUKeysetPre(tv_lval /*base*/, StringData* /*key*/) {
throwInvalidKeysetOperation();
}
[[noreturn]]
inline tv_lval ElemUKeysetPre(tv_lval base, TypedValue key) {
auto const dt = key.m_type;
if (isIntType(dt)) ElemUKeysetPre(base, key.m_data.num);
if (isStringType(dt)) ElemUKeysetPre(base, key.m_data.pstr);
throwInvalidArrayKeyException(&key, val(base).parr);
}
template <KeyType keyType>
[[noreturn]]
inline tv_lval ElemUKeyset(tv_lval base, key_type<keyType> key) {
assertx(tvIsKeyset(base));
assertx(tvIsPlausible(*base));
ElemUKeysetPre(base, key);
}
/**
* ElemU when base is an Object
*/
template <KeyType keyType>
inline tv_lval ElemUObject(tv_lval base, key_type<keyType> key) {
auto obj = val(base).pobj;
failOnNonCollectionObjArrayAccess(obj);
auto const scratchKey = initScratchKey(key);
return collections::atLval(obj, &scratchKey);
}
/*
* Intermediate Elem operation for an unsetting member instruction.
*/
template <KeyType keyType = KeyType::Any>
tv_lval ElemU(tv_lval base, key_type<keyType> key) {
assertx(tvIsPlausible(*base));
// ElemU helpers hand out lvals to immutable_null_base in cases where we know
// it won't be updated. Confirm that we never do an illegal update on it.
assertx(type(immutable_null_base) == KindOfNull);
if (tvIsArrayLike(base) && !base.val().parr->isVanilla()) {
return ElemUBespoke<keyType>(base, key);
}
switch (type(base)) {
case KindOfUninit:
case KindOfNull:
case KindOfBoolean:
case KindOfInt64:
case KindOfDouble:
case KindOfResource:
// Unset on scalar base never modifies the base, but the const_cast is
// necessary to placate the type system.
return const_cast<TypedValue*>(&immutable_uninit_base);
case KindOfClass:
case KindOfLazyClass:
raise_error(Strings::OP_NOT_SUPPORTED_CLASS);
return nullptr;
case KindOfRFunc:
raise_error(Strings::RFUNC_NOT_SUPPORTED);
return nullptr;
case KindOfFunc:
raise_error(Strings::OP_NOT_SUPPORTED_FUNC);
return nullptr;
case KindOfPersistentString:
case KindOfString:
raise_error(Strings::OP_NOT_SUPPORTED_STRING);
return nullptr;
case KindOfClsMeth:
raise_error(Strings::CLS_METH_NOT_SUPPORTED);
case KindOfRClsMeth:
raise_error(Strings::RCLS_METH_NOT_SUPPORTED);
return nullptr;
case KindOfPersistentKeyset:
case KindOfKeyset:
return ElemUKeyset<keyType>(base, key);
case KindOfPersistentDict:
case KindOfDict:
return ElemUDict<keyType>(base, key);
case KindOfPersistentVec:
case KindOfVec:
return ElemUVec<keyType>(base, key);
case KindOfObject:
return ElemUObject<keyType>(base, key);
}
unknownBaseType(type(base));
}
/**
* NewElem when base is Null
*/
[[noreturn]]
inline tv_lval NewElemEmptyish(tv_lval base) {
detail::raiseFalseyPromotion(base);
not_reached();
}
/**
* NewElem when base is an invalid type (number, boolean, string, etc.) and is
* not falsey. We can use immutable_null_base here because updates will raise.
*/
inline tv_lval NewElemInvalid() {
raise_warning("Cannot use a scalar value as an array");
return const_cast<TypedValue*>(&immutable_uninit_base);
}
/**
* NewElem when base is a Boolean
*/
inline tv_lval NewElemBoolean(tv_lval base) {
return val(base).num ? NewElemInvalid() : NewElemEmptyish(base);
}
/**
* NewElem when base is a String
*/
inline tv_lval NewElemString(tv_lval base) {
return val(base).pstr->size() ? NewElemInvalid() : NewElemEmptyish(base);
}
/**
* NewElem when base is an Object
*/
[[noreturn]]
inline tv_lval NewElemObject(tv_lval base) {
failOnNonCollectionObjArrayAccess(val(base).pobj);
throw_cannot_use_newelem_for_lval_read_col();
not_reached();
}
/**
* $result = ($base[] = ...);
*/
inline tv_lval NewElem(tv_lval base) {
assertx(tvIsPlausible(base.tv()));
switch (base.type()) {
case KindOfUninit:
case KindOfNull:
return NewElemEmptyish(base);
case KindOfBoolean:
return NewElemBoolean(base);
case KindOfInt64:
case KindOfDouble:
case KindOfResource:
case KindOfRFunc:
case KindOfFunc:
case KindOfRClsMeth:
case KindOfClass:
case KindOfLazyClass:
return NewElemInvalid();
case KindOfPersistentString:
case KindOfString:
return NewElemString(base);
case KindOfPersistentVec:
case KindOfVec:
case KindOfPersistentDict:
case KindOfDict:
case KindOfPersistentKeyset:
case KindOfKeyset:
throw_cannot_use_newelem_for_lval_read(val(base).parr);
case KindOfObject:
return NewElemObject(base);
case KindOfClsMeth:
throw_cannot_use_newelem_for_lval_read_clsmeth();
}
unknownBaseType(type(base));
}
/**
* SetElem when base is Null
*/
[[noreturn]]
inline void SetElemEmptyish(tv_lval base) {
detail::raiseFalseyPromotion(base);
not_reached();
}
/**
* SetElem when base is an Int64, Double, Resource, Func, or Class.
*/
template <bool setResult>
inline void SetElemScalar(TypedValue* value) {
raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY);
if (!setResult) {
throw InvalidSetMException(make_tv<KindOfNull>());
}
tvDecRefGen(value);
tvWriteNull(*value);
}
/**
* SetElem when base is a Boolean
*/
template <bool setResult>
inline void SetElemBoolean(tv_lval base, TypedValue* value) {
return val(base).num ? SetElemScalar<setResult>(value)
: SetElemEmptyish(base);
}
/**
* Convert a key to integer for SetElem
*/
template<KeyType keyType>
inline int64_t castKeyToInt(key_type<keyType> key) {
return tvToInt(initScratchKey(key));
}
template<>
inline int64_t castKeyToInt<KeyType::Int>(int64_t key) {
return key;
}
/**
* SetElem when base is a String
*/
template <bool setResult, KeyType keyType>
inline StringData* SetElemString(tv_lval base, key_type<keyType> key,
TypedValue* value) {
auto const baseLen = val(base).pstr->size();
if (baseLen == 0) {
SetElemEmptyish(base);
return nullptr;
}
// Convert key to string offset.
auto const x = castKeyToInt<keyType>(key);
if (UNLIKELY(x < 0 || x >= StringData::MaxSize)) {
// Can't use PRId64 here because of order of inclusion issues
raise_warning("Illegal string offset: %lld", (long long)x);
if (!setResult) {
throw InvalidSetMException(make_tv<KindOfNull>());
}
tvDecRefGen(value);
tvWriteNull(*value);
return nullptr;
}
// Compute how long the resulting string will be. Type needs
// to agree with x.
int64_t slen;
if (x >= baseLen) {
slen = x + 1;
} else {
slen = baseLen;
}
// Extract the first character of (string)value.
char y;
{
StringData* valStr;
if (LIKELY(isStringType(value->m_type))) {
valStr = value->m_data.pstr;
valStr->incRefCount();
} else {
valStr = tvCastToStringData(*value);
}
y = valStr->data()[0];
decRefStr(valStr);
}
// Create and save the result.
assertx(x >= 0); // x < 0 is handled above.
auto const oldp = val(base).pstr;
if (x < baseLen && !oldp->cowCheck()) {
// Modify base in place. This is safe because the LHS owns the
// only reference.
FOLLY_SDT(hhvm, hhvm_mut_modifychar, baseLen, x);
auto const newp = oldp->modifyChar(x, y);
if (UNLIKELY(newp != oldp)) {
// only way we can get here is due to a private (count==1) apc string.
decRefStr(oldp);
val(base).pstr = newp;
type(base) = KindOfString;
}
// NB: if x < capacity, we could have appended in-place here.
} else {
FOLLY_SDT(hhvm, hhvm_cow_modifychar, baseLen, x);
StringData* sd = StringData::Make(slen);
char* s = sd->mutableData();
memcpy(s, oldp->data(), baseLen);
if (x > baseLen) {
memset(&s[baseLen], ' ', slen - baseLen - 1);
}
s[x] = y;
sd->setSize(slen);
decRefStr(oldp);
val(base).pstr = sd;
type(base) = KindOfString;
}
return makeStaticString(y);
}
/**
* SetElem when base is an Object
*/
template <KeyType keyType>
inline void SetElemObject(tv_lval base, key_type<keyType> key,
TypedValue* value) {
auto obj = val(base).pobj;
failOnNonCollectionObjArrayAccess(obj);
auto const scratchKey = initScratchKey(key);
collections::set(obj, &scratchKey, value);
}
/*
* arraySetUpdateBase is used by SetElem{Array,Vec,Dict} to do the necessary
* bookkeeping after mutating an array.
*/
ALWAYS_INLINE
void arraySetUpdateBase(ArrayData* newData, tv_lval base) {
assertx(newData->hasExactlyOneRef());
assertx(isArrayLikeType(type(base)));
type(base) = dt_with_rc(type(base));
val(base).parr = newData;
assertx(type(base) == newData->toDataType());
assertx(tvIsPlausible(*base));
}
/**
* SetElem when base is a Vec
*/
inline ArrayData* SetElemVecPre(ArrayData* a, int64_t key, TypedValue* value) {
tvIncRefGen(*value);
return VanillaVec::SetIntMove(a, key, *value);
}
inline ArrayData*
SetElemVecPre(ArrayData* a, StringData* key, TypedValue* /*value*/) {
throwInvalidArrayKeyException(key, a);
}
inline ArrayData*
SetElemVecPre(ArrayData* a, TypedValue key, TypedValue* value) {
if (tvIsInt(key)) return SetElemVecPre(a, key.m_data.num, value);
if (tvIsString(key)) return SetElemVecPre(a, key.m_data.pstr, value);
throwInvalidArrayKeyException(&key, a);
}
template <KeyType keyType>
inline void SetElemVec(tv_lval base, key_type<keyType> key, TypedValue* value) {
assertx(tvIsVec(base));
assertx(tvIsPlausible(*base));
ArrayData* a = val(base).parr;
auto const newData = SetElemVecPre(a, key, value);
arraySetUpdateBase(newData, base);
}
/**
* SetElem when base is a Dict
*/
inline ArrayData* SetElemDictPre(ArrayData* a, int64_t key, TypedValue* value) {
tvIncRefGen(*value);
return VanillaDict::SetIntMove(a, key, *value);
}
inline ArrayData*
SetElemDictPre(ArrayData* a, StringData* key, TypedValue* value) {
tvIncRefGen(*value);
return VanillaDict::SetStrMove(a, key, *value);
}
inline ArrayData*
SetElemDictPre(ArrayData* a, TypedValue key, TypedValue* value) {
if (tvIsInt(key)) return SetElemDictPre(a, key.m_data.num, value);
if (tvIsString(key)) return SetElemDictPre(a, key.m_data.pstr, value);
throwInvalidArrayKeyException(&key, a);
}
template <KeyType keyType>
inline void SetElemDict(tv_lval base, key_type<keyType> key,
TypedValue* value) {
assertx(tvIsDict(base));
assertx(tvIsPlausible(*base));
ArrayData* a = val(base).parr;
auto const newData = SetElemDictPre(a, key, value);
arraySetUpdateBase(newData, base);
}
/**
* SetElem when base is a bespoke vec or dict
*/
inline ArrayData* SetElemBespokePre(
ArrayData* a, int64_t key, TypedValue* value) {
tvIncRefGen(*value);
return BespokeArray::SetIntMove(a, key, *value);
}
inline ArrayData* SetElemBespokePre(
ArrayData* a, StringData* key, TypedValue* value) {
tvIncRefGen(*value);
return BespokeArray::SetStrMove(a, key, *value);
}
inline ArrayData* SetElemBespokePre(
ArrayData* a, TypedValue key, TypedValue* value) {
if (tvIsInt(key)) return SetElemBespokePre(a, key.m_data.num, value);
if (tvIsString(key)) return SetElemBespokePre(a, key.m_data.pstr, value);
throwInvalidArrayKeyException(&key, a);
}
template <KeyType keyType>
inline void SetElemBespoke(
tv_lval base, key_type<keyType> key, TypedValue* value) {
assertx(tvIsArrayLike(base));
assertx(tvIsPlausible(*base));
auto const oldArr = base.val().parr;
assertx(!oldArr->isVanilla());
auto const result = SetElemBespokePre(oldArr, key, value);
arraySetUpdateBase(result, base);
}
/**
* SetElem() leaves the result in 'value', rather than returning it as in
* SetOpElem(), because doing so avoids a dup operation that SetOpElem() can't
* get around.
*/
template <bool setResult, KeyType keyType>
NEVER_INLINE
StringData* SetElemSlow(tv_lval base, key_type<keyType> key,
TypedValue* value) {
assertx(tvIsPlausible(*base));
switch (type(base)) {
case KindOfUninit:
case KindOfNull:
SetElemEmptyish(base);
return nullptr;
case KindOfBoolean:
SetElemBoolean<setResult>(base, value);
return nullptr;
case KindOfInt64:
case KindOfDouble:
case KindOfResource:
case KindOfRFunc:
case KindOfFunc:
case KindOfRClsMeth:
case KindOfClass:
case KindOfLazyClass:
SetElemScalar<setResult>(value);
return nullptr;
case KindOfPersistentString:
case KindOfString:
return SetElemString<setResult, keyType>(base, key, value);
case KindOfPersistentKeyset:
case KindOfKeyset:
throwInvalidKeysetOperation();
// Handled in SetElem
case KindOfPersistentVec:
case KindOfVec:
case KindOfPersistentDict:
case KindOfDict:
always_assert(false);
case KindOfObject:
SetElemObject<keyType>(base, key, value);
return nullptr;
case KindOfClsMeth:
SetElemScalar<setResult>(value);
return nullptr;
}
unknownBaseType(type(base));
}
/**
* Fast path for SetElem assuming base is an Array
*/
template <bool setResult, KeyType keyType = KeyType::Any>
inline StringData* SetElem(tv_lval base, key_type<keyType> key,
TypedValue* value) {
assertx(tvIsPlausible(*base));
if (LIKELY(tvIsVec(base))) {
base.val().parr->isVanilla() ? SetElemVec<keyType>(base, key, value)
: SetElemBespoke<keyType>(base, key, value);
return nullptr;
}
if (LIKELY(tvIsDict(base))) {
base.val().parr->isVanilla() ? SetElemDict<keyType>(base, key, value)
: SetElemBespoke<keyType>(base, key, value);
return nullptr;
}
return SetElemSlow<setResult, keyType>(base, key, value);
}
template<bool reverse>
void SetRange(
tv_lval base, int64_t offset, TypedValue src, int64_t count, int64_t size
);
/**
* SetNewElem when base is Null
*/
[[noreturn]]
inline void SetNewElemEmptyish(tv_lval base) {
detail::raiseFalseyPromotion(base);
not_reached();
}
/**
* SetNewElem when base is Int64, Double, Resource, Func or Class
*/
template <bool setResult>
inline void SetNewElemScalar(TypedValue* value) {
raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY);
if (!setResult) {
throw InvalidSetMException(make_tv<KindOfNull>());
}
tvDecRefGen(value);
tvWriteNull(*value);
}
/**
* SetNewElem when base is a Boolean
*/
template <bool setResult>
inline void SetNewElemBoolean(tv_lval base, TypedValue* value) {
return val(base).num ? SetNewElemScalar<setResult>(value)
: SetNewElemEmptyish(base);
}
/**
* SetNewElem when base is a String
*/
[[noreturn]]
inline void SetNewElemString(tv_lval base) {
if (!val(base).pstr->size()) SetNewElemEmptyish(base);
raise_error("[] operator not supported for strings");
}
/**
* SetNewElem when base is a bespoke array-like
*/
inline void SetNewElemBespoke(tv_lval base, TypedValue* value) {
assertx(tvIsArrayLike(base));
assertx(tvIsPlausible(*base));
auto const oldArr = base.val().parr;
auto const result = BespokeArray::AppendMove(oldArr, *value);
arraySetUpdateBase(result, base);
assertx(tvIsPlausible(*base));
}
/**
* SetNewElem when base is a Vec
*/
inline void SetNewElemVec(tv_lval base, TypedValue* value) {
assertx(tvIsVec(base));
assertx(tvIsPlausible(*base));
auto a = val(base).parr;
auto a2 = VanillaVec::AppendMove(a, *value);
arraySetUpdateBase(a2, base);
}
/**
* SetNewElem when base is a Dict
*/
inline void SetNewElemDict(tv_lval base, TypedValue* value) {
assertx(tvIsDict(base));
assertx(tvIsPlausible(*base));
auto a = val(base).parr;
auto a2 = VanillaDict::AppendMove(a, *value);
arraySetUpdateBase(a2, base);
}
/**
* SetNewElem when base is a Keyset
*/
inline void SetNewElemKeyset(tv_lval base, TypedValue* value) {
assertx(tvIsKeyset(base));
assertx(tvIsPlausible(*base));
auto a = val(base).parr;
auto a2 = VanillaKeyset::AppendMove(a, *value);
if (a2 != a) {
type(base) = KindOfKeyset;
val(base).parr = a2;
assertx(tvIsPlausible(*base));
}
}
/**
* SetNewElem when base is an Object
*/
inline void SetNewElemObject(tv_lval base, TypedValue* value) {
auto obj = val(base).pobj;
failOnNonCollectionObjArrayAccess(obj);
collections::append(obj, value);
}
/**
* $base[] = ...
*/
template <bool setResult>
inline void SetNewElem(tv_lval base, TypedValue* value) {
assertx(tvIsPlausible(*base));
if (tvIsArrayLike(base) && !base.val().parr->isVanilla()) {
tvIncRefGen(*value);
return SetNewElemBespoke(base, value);
}
switch (type(base)) {
case KindOfUninit:
case KindOfNull:
return SetNewElemEmptyish(base);
case KindOfBoolean:
return SetNewElemBoolean<setResult>(base, value);
case KindOfInt64:
case KindOfDouble:
case KindOfResource:
case KindOfRFunc:
case KindOfFunc:
case KindOfRClsMeth:
case KindOfClass:
case KindOfLazyClass:
return SetNewElemScalar<setResult>(value);
case KindOfPersistentString:
case KindOfString:
return SetNewElemString(base);
case KindOfPersistentVec:
case KindOfVec:
tvIncRefGen(*value);
return SetNewElemVec(base, value);
case KindOfPersistentDict:
case KindOfDict:
tvIncRefGen(*value);
return SetNewElemDict(base, value);
case KindOfPersistentKeyset:
case KindOfKeyset:
tvIncRefGen(*value);
return SetNewElemKeyset(base, value);
case KindOfObject:
return SetNewElemObject(base, value);
case KindOfClsMeth:
return SetNewElemScalar<setResult>(value);
}
unknownBaseType(type(base));
}
/**
* SetOpElem when base is Null
*/
[[noreturn]]
inline TypedValue SetOpElemEmptyish(tv_lval base) {
detail::raiseFalseyPromotion(base);
not_reached();
}
/**
* TypedValue when base is Int64, Double, Resource, Func, or Class
*/
inline TypedValue SetOpElemScalar() {
raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY);
return make_tv<KindOfNull>();
}
/*
* Perform the operation `update` on a given BespokeArray safely, and with
* minimum vanilla escalation. To do so, we call Elem, and store the value
* in a tmp TypedValue, and update it; if its type is unchanged, we write
* the value back directly, but if the type is changed, we do a full set.
*/
template <typename Update>
TypedValue UpdateBespoke(tv_lval base, TypedValue key, Update update) {
auto const val = ElemDBespoke<KeyType::Any>(base, key);
TypedValue tmp = *val;
if (val.type() == KindOfString) {
val.val().pstr = staticEmptyString();
} else {
tvIncRefGen(tmp);
}
auto const result = update(&tmp);
if (val.type() == tmp.type()) {
val.val() = tmp.val();
if (val.type() != KindOfString) {
tvDecRefGen(tmp);
}
} else {
SetElemBespoke<KeyType::Any>(base, key, &tmp);
}
return result;
}
/**
* $result = ($base[$x] <op>= $y)
*/
inline TypedValue SetOpElem(SetOpOp op, tv_lval base,
TypedValue key, TypedValue* rhs) {
assertx(tvIsPlausible(*base));
if (tvIsArrayLike(base) && !base.val().parr->isVanilla()) {
if (base.val().parr->isKeysetType()) throwInvalidKeysetOperation();
return UpdateBespoke(base, key, [&](auto lval) {
setopBody(lval, op, rhs);
return *lval;
});
}
auto const handleVec = [&] {
auto const result = ElemDVec<KeyType::Any>(base, key);
setopBody(tvAssertPlausible(result), op, rhs);
return *result;
};
switch (type(base)) {
case KindOfUninit:
case KindOfNull:
return SetOpElemEmptyish(base);
case KindOfBoolean:
return val(base).num ? SetOpElemScalar() : SetOpElemEmptyish(base);
case KindOfInt64:
case KindOfDouble:
case KindOfResource:
case KindOfRFunc:
case KindOfFunc:
case KindOfRClsMeth:
case KindOfClass:
case KindOfLazyClass:
return SetOpElemScalar();
case KindOfPersistentString:
case KindOfString:
if (val(base).pstr->size() != 0) {
raise_error("Cannot use assign-op operators with overloaded "
"objects nor string offsets");
}
return SetOpElemEmptyish(base);
case KindOfPersistentKeyset:
case KindOfKeyset:
throwInvalidKeysetOperation();
case KindOfPersistentDict:
case KindOfDict: {
auto const result = ElemDDict<KeyType::Any>(base, key);
setopBody(tvAssertPlausible(result), op, rhs);
return *result;
}
case KindOfPersistentVec:
case KindOfVec:
return handleVec();
case KindOfObject: {
auto obj = val(base).pobj;
failOnNonCollectionObjArrayAccess(obj);
auto const result = collections::atRw(obj, &key);
setopBody(result, op, rhs);
return *result;
}
case KindOfClsMeth:
return SetOpElemScalar();
}
unknownBaseType(type(base));
}
[[noreturn]]
inline TypedValue SetOpNewElemEmptyish(tv_lval base) {
detail::raiseFalseyPromotion(base);
not_reached();
}
inline TypedValue SetOpNewElemScalar() {
raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY);
return make_tv<KindOfNull>();
}
inline TypedValue SetOpNewElem(SetOpOp op, tv_lval base, TypedValue* rhs) {
assertx(tvIsPlausible(*base));
switch (type(base)) {
case KindOfUninit:
case KindOfNull:
return SetOpNewElemEmptyish(base);
case KindOfBoolean:
return val(base).num ? SetOpNewElemScalar() : SetOpNewElemEmptyish(base);
case KindOfInt64:
case KindOfDouble:
case KindOfResource:
case KindOfRFunc:
case KindOfFunc:
case KindOfRClsMeth:
case KindOfClass:
case KindOfLazyClass:
return SetOpNewElemScalar();
case KindOfPersistentString:
case KindOfString:
if (val(base).pstr->size() != 0) {
raise_error("[] operator not supported for strings");
}
return SetOpNewElemEmptyish(base);
case KindOfPersistentVec:
case KindOfVec:
case KindOfPersistentDict:
case KindOfDict:
case KindOfPersistentKeyset:
case KindOfKeyset:
throw_cannot_use_newelem_for_lval_read(val(base).parr);
case KindOfObject: {
failOnNonCollectionObjArrayAccess(val(base).pobj);
throw_cannot_use_newelem_for_lval_read_col();
}
case KindOfClsMeth:
throw_cannot_use_newelem_for_lval_read_clsmeth();
}
unknownBaseType(type(base));
}
NEVER_INLINE TypedValue incDecBodySlow(IncDecOp op, tv_lval fr);
inline TypedValue IncDecBody(IncDecOp op, tv_lval fr) {
assertx(tvIsPlausible(*fr));
if (UNLIKELY(!isIntType(type(fr)))) {
return incDecBodySlow(op, fr);
}
// fast cases, assuming integers overflow to ints. Because int64_t overflow is
// undefined behavior reinterpret_cast<uint64_t&> first
switch (op) {
case IncDecOp::PreInc:
++reinterpret_cast<uint64_t&>(val(fr).num);
return *fr;
case IncDecOp::PostInc: {
auto const tmp = *fr;
++reinterpret_cast<uint64_t&>(val(fr).num);
return tmp;
}
case IncDecOp::PreDec:
--reinterpret_cast<uint64_t&>(val(fr).num);
return *fr;
case IncDecOp::PostDec: {
auto const tmp = *fr;
--reinterpret_cast<uint64_t&>(val(fr).num);
return tmp;
}
default:
return incDecBodySlow(op, fr);
}
}
[[noreturn]]
inline TypedValue IncDecElemEmptyish(tv_lval base) {
detail::raiseFalseyPromotion(base);
not_reached();
}
inline TypedValue IncDecElemScalar() {
raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY);
return make_tv<KindOfNull>();
}
inline TypedValue IncDecElem(IncDecOp op, tv_lval base, TypedValue key) {
assertx(tvIsPlausible(*base));
if (tvIsArrayLike(base) && !base.val().parr->isVanilla()) {
return UpdateBespoke(base, key, [&](auto lval) {
return IncDecBody(op, lval);
});
}
switch (type(base)) {
case KindOfUninit:
case KindOfNull:
return IncDecElemEmptyish(base);
case KindOfBoolean:
return val(base).num ? IncDecElemScalar() : IncDecElemEmptyish(base);
case KindOfInt64:
case KindOfDouble:
case KindOfResource:
case KindOfRFunc:
case KindOfFunc:
case KindOfRClsMeth:
case KindOfClass:
case KindOfLazyClass:
return IncDecElemScalar();
case KindOfPersistentString:
case KindOfString:
if (val(base).pstr->size() != 0) {
raise_error("Cannot increment/decrement overloaded objects "
"nor string offsets");
}
return IncDecElemEmptyish(base);
case KindOfPersistentKeyset:
case KindOfKeyset:
throwInvalidKeysetOperation();
case KindOfPersistentDict:
case KindOfDict:
return IncDecBody(op, ElemDDict<KeyType::Any>(base, key));
case KindOfPersistentVec:
case KindOfVec:
return IncDecBody(op, ElemDVec<KeyType::Any>(base, key));
case KindOfObject: {
tv_lval result;
auto localTvRef = make_tv<KindOfUninit>();
auto obj = val(base).pobj;
failOnNonCollectionObjArrayAccess(obj);
result = collections::atRw(obj, &key);
assertx(tvIsPlausible(*result));
auto const dest = IncDecBody(op, result);
tvDecRefGen(localTvRef);
return dest;
}
case KindOfClsMeth:
return IncDecElemScalar();
}
unknownBaseType(type(base));
}
[[noreturn]]
inline TypedValue IncDecNewElemEmptyish(tv_lval base) {
detail::raiseFalseyPromotion(base);
not_reached();
}
inline TypedValue IncDecNewElemScalar() {
raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY);
return make_tv<KindOfNull>();
}
inline TypedValue IncDecNewElem(IncDecOp op, tv_lval base) {
assertx(tvIsPlausible(*base));
switch (type(base)) {
case KindOfUninit:
case KindOfNull:
return IncDecNewElemEmptyish(base);
case KindOfBoolean:
return val(base).num ? IncDecNewElemScalar()
: IncDecNewElemEmptyish(base);
case KindOfInt64:
case KindOfDouble:
case KindOfResource:
case KindOfRFunc:
case KindOfFunc:
case KindOfRClsMeth:
case KindOfClass:
case KindOfLazyClass:
return IncDecNewElemScalar();
case KindOfPersistentString:
case KindOfString:
if (val(base).pstr->size() != 0) {
raise_error("[] operator not supported for strings");
}
return IncDecNewElemEmptyish(base);
case KindOfPersistentVec:
case KindOfVec:
case KindOfPersistentDict:
case KindOfDict:
case KindOfPersistentKeyset:
case KindOfKeyset:
throw_cannot_use_newelem_for_lval_read(val(base).parr);
case KindOfObject: {
failOnNonCollectionObjArrayAccess(val(base).pobj);
throw_cannot_use_newelem_for_lval_read_col();
}
case KindOfClsMeth:
throw_cannot_use_newelem_for_lval_read_clsmeth();
}
unknownBaseType(type(base));
}
/**
* UnsetElem when base is a Vec
*/
inline ArrayData* UnsetElemVecPre(ArrayData* a, int64_t key) {
return VanillaVec::RemoveIntMove(a, key);
}
inline ArrayData*
UnsetElemVecPre(ArrayData* a, StringData* /*key*/) {
/* Never contains strings, so a no-op. */
return a;
}
inline ArrayData* UnsetElemVecPre(ArrayData* a, TypedValue key) {
auto const dt = key.m_type;
if (LIKELY(isIntType(dt))) return UnsetElemVecPre(a, key.m_data.num);
if (isStringType(dt)) return UnsetElemVecPre(a, key.m_data.pstr);
throwInvalidArrayKeyException(&key, a);
}
template <KeyType keyType>
inline void UnsetElemVec(tv_lval base, key_type<keyType> key) {
assertx(tvIsVec(base));
assertx(tvIsPlausible(*base));
ArrayData* a = val(base).parr;
ArrayData* a2 = UnsetElemVecPre(a, key);
type(base) = dt_with_rc(type(base));
val(base).parr = a2;
}
/**
* UnsetElem when base is a Dict
*/
inline ArrayData* UnsetElemDictPre(ArrayData* a, int64_t key) {
return VanillaDict::RemoveIntMove(a, key);
}
inline ArrayData* UnsetElemDictPre(ArrayData* a, StringData* key) {
return VanillaDict::RemoveStrMove(a, key);
}
inline ArrayData* UnsetElemDictPre(ArrayData* a, TypedValue key) {
auto const dt = key.m_type;
if (isIntType(dt)) return UnsetElemDictPre(a, key.m_data.num);
if (isStringType(dt)) return UnsetElemDictPre(a, key.m_data.pstr);
throwInvalidArrayKeyException(&key, a);
}
template <KeyType keyType>
inline void UnsetElemDict(tv_lval base, key_type<keyType> key) {
assertx(tvIsDict(base));
assertx(tvIsPlausible(*base));
ArrayData* a = val(base).parr;
ArrayData* a2 = UnsetElemDictPre(a, key);
type(base) = dt_with_rc(type(base));
val(base).parr = a2;
}
/**
* UnsetElem when base is a Keyset
*/
inline ArrayData* UnsetElemKeysetPre(ArrayData* a, int64_t key) {
return VanillaKeyset::RemoveIntMove(a, key);
}
inline ArrayData* UnsetElemKeysetPre(ArrayData* a, StringData* key) {
return VanillaKeyset::RemoveStrMove(a, key);
}
inline ArrayData* UnsetElemKeysetPre(ArrayData* a, TypedValue key) {
auto const dt = key.m_type;
if (isIntType(dt)) return UnsetElemKeysetPre(a, key.m_data.num);
if (isStringType(dt)) return UnsetElemKeysetPre(a, key.m_data.pstr);
throwInvalidArrayKeyException(&key, a);
}
template <KeyType keyType>
inline void UnsetElemKeyset(tv_lval base, key_type<keyType> key) {
assertx(tvIsKeyset(base));
assertx(tvIsPlausible(*base));
ArrayData* a = val(base).parr;
ArrayData* a2 = UnsetElemKeysetPre(a, key);
type(base) = KindOfKeyset;
val(base).parr = a2;
}
/**
* UnsetElem when base is a bespoke Hack array
*/
inline ArrayData* UnsetElemBespokePre(ArrayData* a, int64_t key) {
return BespokeArray::RemoveIntMove(a, key);
}
inline ArrayData* UnsetElemBespokePre(ArrayData* a, StringData* key) {
return BespokeArray::RemoveStrMove(a, key);
}
inline ArrayData* UnsetElemBespokePre(ArrayData* a, TypedValue key) {
if (tvIsInt(key)) return UnsetElemBespokePre(a, key.m_data.num);
if (tvIsString(key)) return UnsetElemBespokePre(a, key.m_data.pstr);
throwInvalidArrayKeyException(&key, a);
}
template <KeyType keyType>
inline void UnsetElemBespoke(tv_lval base, key_type<keyType> key) {
assertx(tvIsArrayLike(base));
assertx(tvIsPlausible(*base));
auto const oldArr = base.val().parr;
assertx(!oldArr->isVanilla());
auto const result = UnsetElemBespokePre(oldArr, key);
if (result != oldArr) {
type(base) = dt_with_rc(type(base));
val(base).parr = result;
}
assertx(tvIsPlausible(*base));
}
/**
* unset($base[$member])
*/
template <KeyType keyType>
NEVER_INLINE
void UnsetElemSlow(tv_lval base, key_type<keyType> key) {
assertx(tvIsPlausible(*base));
switch (type(base)) {
case KindOfUninit:
case KindOfNull:
case KindOfBoolean:
case KindOfInt64:
case KindOfDouble:
case KindOfResource:
return; // Do nothing.
case KindOfRFunc:
raise_error("Cannot unset a reified function");
return;
case KindOfFunc:
raise_error("Cannot unset a func");
return;
case KindOfRClsMeth:
raise_error("Cannot unset a reified class method pointer");
return;
case KindOfClass:
case KindOfLazyClass:
raise_error("Cannot unset a class");
return;
case KindOfPersistentString:
case KindOfString:
raise_error(Strings::CANT_UNSET_STRING);
return;
// Handled in UnsetElem
case KindOfPersistentVec:
case KindOfVec:
case KindOfPersistentDict:
case KindOfDict:
case KindOfPersistentKeyset:
case KindOfKeyset:
always_assert(false);
case KindOfObject: {
auto obj = val(base).pobj;
failOnNonCollectionObjArrayAccess(obj);
auto const& scratchKey = initScratchKey(key);
collections::unset(obj, &scratchKey);
return;
}
case KindOfClsMeth:
raise_error("Cannot unset a class method pointer");
}
unknownBaseType(type(base));
}
/**
* Fast path for UnsetElem assuming base is an Array
*/
template <KeyType keyType = KeyType::Any>
inline void UnsetElem(tv_lval base, key_type<keyType> key) {
assertx(tvIsPlausible(*base));
if (tvIsArrayLike(base)) {
auto const ad = base.val().parr;
if (!ad->isVanilla()) return UnsetElemBespoke<keyType>(base, key);
if (tvIsVec(base)) return UnsetElemVec<keyType>(base, key);
if (tvIsDict(base)) return UnsetElemDict<keyType>(base, key);
return UnsetElemKeyset<keyType>(base, key);
}
return UnsetElemSlow<keyType>(base, key);
}
/**
* IssetElem when base is an Object
*/
template<KeyType keyType>
bool IssetElemObj(ObjectData* instance, key_type<keyType> key) {
failOnNonCollectionObjArrayAccess(instance);
auto scratchKey = initScratchKey(key);
return collections::isset(instance, &scratchKey);
}
/**
* IssetElem when base is a String
*/
template<KeyType keyType>
bool IssetElemString(const StringData* sd, key_type<keyType> key) {
auto scratchKey = initScratchKey(key);
int64_t x;
if (LIKELY(scratchKey.m_type == KindOfInt64)) {
x = scratchKey.m_data.num;
} else {
TypedValue tv;
tvDup(scratchKey, tv);
bool badKey = false;
if (isStringType(tv.m_type)) {
const char* str = tv.m_data.pstr->data();
size_t len = tv.m_data.pstr->size();
while (len > 0 &&
(*str == ' ' || *str == '\t' || *str == '\r' || *str == '\n')) {
++str;
--len;
}
int64_t n;
badKey = !is_strictly_integer(str, len, n);
} else if (isArrayLikeType(tv.m_type) || tv.m_type == KindOfObject ||
tv.m_type == KindOfResource) {
badKey = true;
}
// Even if badKey == true, we still perform the cast so that we
// raise the appropriate warnings.
tvCastToInt64InPlace(&tv);
if (badKey) {
return false;
}
x = tv.m_data.num;
}
return x >= 0 && x < sd->size();
}
/**
* IssetElem when base is a Vec
*/
template<KeyType keyType>
bool IssetElemVec(ArrayData* a, key_type<keyType> key) {
assertx(a->isVanillaVec());
auto const result = ElemVec<MOpMode::None, keyType>(a, key);
return !tvIsNull(tvAssertPlausible(result));
}
/**
* IssetElem when base is a Dict
*/
template<KeyType keyType>
bool IssetElemDict(ArrayData* a, key_type<keyType> key) {
assertx(a->isVanillaDict());
auto const result = ElemDict<MOpMode::None, keyType>(a, key);
return !tvIsNull(tvAssertPlausible(result));
}
/**
* IssetElem when base is a Keyset
*/
template<KeyType keyType>
bool IssetElemKeyset(ArrayData* a, key_type<keyType> key) {
assertx(a->isVanillaKeyset());
auto const result = ElemKeyset<MOpMode::None, keyType>(a, key);
return !tvIsNull(tvAssertPlausible(result));
}
/**
* IssetElem when base is a bespoke Hack array
*/
template<KeyType keyType>
bool IssetElemBespoke(ArrayData* a, key_type<keyType> key) {
auto const result = ElemBespoke<MOpMode::None, keyType>(a, key);
return !tvIsNull(tvAssertPlausible(result));
}
/**
* IssetElem when base is a ClsMeth
*/
template<KeyType keyType>
bool IssetElemClsMeth(ClsMethDataRef base, key_type<keyType> key) {
const TypedValue result = ElemClsMethPre<MOpMode::None>(base, key);
return !tvIsNull(tvAssertPlausible(result));
}
/**
* isset($base[$key])
*/
template <KeyType keyType>
NEVER_INLINE bool IssetElemSlow(TypedValue base, key_type<keyType> key) {
assertx(tvIsPlausible(base));
switch (type(base)) {
case KindOfUninit:
case KindOfNull:
case KindOfBoolean:
case KindOfInt64:
case KindOfDouble:
case KindOfResource:
case KindOfRFunc:
case KindOfRClsMeth:
case KindOfFunc:
return false;
case KindOfClass:
return IssetElemString<keyType>(
classToStringHelper(val(base).pclass), key
);
case KindOfLazyClass:
return IssetElemString<keyType>(
lazyClassToStringHelper(val(base).plazyclass), key
);
case KindOfPersistentString:
case KindOfString:
return IssetElemString<keyType>(val(base).pstr, key);
// These types are handled in IssetElem.
case KindOfPersistentVec:
case KindOfVec:
case KindOfPersistentDict:
case KindOfDict:
case KindOfPersistentKeyset:
case KindOfKeyset:
always_assert(false);
case KindOfObject:
return IssetElemObj<keyType>(val(base).pobj, key);
case KindOfClsMeth:
return false;
}
unknownBaseType(type(base));
}
template <KeyType keyType = KeyType::Any>
bool IssetElem(TypedValue base, key_type<keyType> key) {
assertx(tvIsPlausible(base));
if (tvIsArrayLike(base)) {
auto const ad = val(base).parr;
if (!ad->isVanilla()) {
return IssetElemBespoke<keyType>(ad, key);
} else if (ad->isVanillaVec()) {
return IssetElemVec<keyType>(ad, key);
} else if (ad->isVanillaDict()) {
return IssetElemDict<keyType>(ad, key);
} else {
return IssetElemKeyset<keyType>(ad, key);
}
}
return IssetElemSlow<keyType>(base, key);
}
template<MOpMode mode>
inline tv_lval propPreNull(TypedValue& tvRef) {
tvWriteNull(tvRef);
if (mode == MOpMode::Warn) {
raise_notice("Cannot access property on non-object");
}
return tv_lval(&tvRef);
}
template<MOpMode mode>
tv_lval propPreStdclass(TypedValue& tvRef) {
if (mode != MOpMode::Define) return propPreNull<mode>(tvRef);
detail::raiseEmptyObject();
not_reached();
}
template<MOpMode mode>
inline tv_lval nullSafeProp(TypedValue& tvRef,
Class* ctx,
TypedValue base,
StringData* key,
ReadonlyOp op) {
switch (base.type()) {
case KindOfUninit:
case KindOfNull:
tvWriteNull(tvRef);
return &tvRef;
case KindOfBoolean:
case KindOfInt64:
case KindOfDouble:
case KindOfResource:
case KindOfPersistentString:
case KindOfString:
case KindOfPersistentVec:
case KindOfVec:
case KindOfPersistentDict:
case KindOfDict:
case KindOfPersistentKeyset:
case KindOfKeyset:
case KindOfRFunc:
case KindOfFunc:
case KindOfClass:
case KindOfLazyClass:
case KindOfClsMeth:
case KindOfRClsMeth:
return propPreNull<mode>(tvRef);
case KindOfObject:
return val(base).pobj->prop(&tvRef, ctx, key, op);
}
not_reached();
}
/*
* Generic property access (PropX and PropDX end up here).
*
* Returns a pointer to a number of possible places.
*/
template<MOpMode mode, KeyType keyType = KeyType::Any>
inline tv_lval PropObj(TypedValue& tvRef, const Class* ctx,
ObjectData* instance, key_type<keyType> key,
ReadonlyOp op) {
auto keySD = prepareKey(key);
SCOPE_EXIT { releaseKey<keyType>(keySD); };
// Get property.
if (mode == MOpMode::Define) {
return instance->propD(&tvRef, ctx, keySD, op);
}
if (mode == MOpMode::None) {
return instance->prop(&tvRef, ctx, keySD, op);
}
if (mode == MOpMode::Warn) {
return instance->propW(&tvRef, ctx, keySD, op);
}
assertx(mode == MOpMode::Unset);
return instance->propU(&tvRef, ctx, keySD, op);
}
template<MOpMode mode, KeyType keyType = KeyType::Any>
inline tv_lval Prop(TypedValue& tvRef, const Class* ctx,
TypedValue base, key_type<keyType> key, ReadonlyOp op) {
if (LIKELY(type(base) == KindOfObject)) {
return PropObj<mode,keyType>(tvRef, ctx, val(base).pobj, key, op);
}
switch (base.type()) {
case KindOfUninit:
case KindOfNull:
return propPreStdclass<mode>(tvRef);
case KindOfBoolean:
return base.val().num ? propPreNull<mode>(tvRef)
: propPreStdclass<mode>(tvRef);
case KindOfInt64:
case KindOfDouble:
case KindOfResource:
case KindOfRFunc:
case KindOfFunc:
case KindOfClass:
case KindOfLazyClass:
return propPreNull<mode>(tvRef);
case KindOfPersistentString:
case KindOfString:
return base.val().pstr->size() ? propPreNull<mode>(tvRef)
: propPreStdclass<mode>(tvRef);
case KindOfPersistentVec:
case KindOfVec:
case KindOfPersistentDict:
case KindOfDict:
case KindOfPersistentKeyset:
case KindOfKeyset:
case KindOfClsMeth:
case KindOfRClsMeth:
return propPreNull<mode>(tvRef);
case KindOfObject:
always_assert(false);
}
unknownBaseType(type(base));
}
template <KeyType kt>
inline bool IssetPropObj(Class* ctx, ObjectData* instance, key_type<kt> key) {
auto keySD = prepareKey(key);
SCOPE_EXIT { releaseKey<kt>(keySD); };
return instance->propIsset(ctx, keySD);
}
template <KeyType kt = KeyType::Any>
bool IssetProp(Class* ctx, TypedValue base, key_type<kt> key) {
if (LIKELY(type(base) == KindOfObject)) {
return IssetPropObj<kt>(ctx, val(base).pobj, key);
}
return false;
}
[[noreturn]]
inline void SetPropNull() {
raise_warning("Cannot access property on non-object");
throw InvalidSetMException(make_tv<KindOfNull>());
}
template <KeyType keyType>
inline void SetPropObj(Class* ctx, ObjectData* instance, key_type<keyType> key,
TypedValue val, ReadonlyOp op) {
StringData* keySD = prepareKey(key);
SCOPE_EXIT { releaseKey<keyType>(keySD); };
// Set property.
instance->setProp(ctx, keySD, val, op);
}
// $base->$key = $val
template <KeyType keyType = KeyType::Any>
inline void SetProp(Class* ctx, TypedValue base, key_type<keyType> key,
TypedValue val, ReadonlyOp op) {
switch (type(base)) {
case KindOfUninit:
case KindOfNull:
return detail::raiseEmptyObject();
case KindOfBoolean:
return HPHP::val(base).num ? SetPropNull()
: detail::raiseEmptyObject();
case KindOfInt64:
case KindOfDouble:
case KindOfPersistentVec:
case KindOfVec:
case KindOfPersistentDict:
case KindOfDict:
case KindOfPersistentKeyset:
case KindOfKeyset:
case KindOfResource:
case KindOfRFunc:
case KindOfFunc:
case KindOfClass:
case KindOfLazyClass:
case KindOfClsMeth:
case KindOfRClsMeth:
return SetPropNull();
case KindOfPersistentString:
case KindOfString:
return HPHP::val(base).pstr->size() ? SetPropNull()
: detail::raiseEmptyObject();
case KindOfObject:
return SetPropObj<keyType>(ctx, HPHP::val(base).pobj, key, val, op);
}
unknownBaseType(type(base));
}
inline tv_lval SetOpPropNull(TypedValue& tvRef) {
raise_warning("Attempt to assign property of non-object");
tvWriteNull(tvRef);
return &tvRef;
}
inline tv_lval SetOpPropObj(TypedValue& tvRef, Class* ctx,
SetOpOp op, ObjectData* instance,
TypedValue key, TypedValue* rhs) {
StringData* keySD = prepareKey(key);
SCOPE_EXIT { decRefStr(keySD); };
return instance->setOpProp(tvRef, ctx, op, keySD, rhs);
}
// $base->$key <op>= $rhs
inline tv_lval SetOpProp(TypedValue& tvRef,
Class* ctx, SetOpOp op,
TypedValue base, TypedValue key,
TypedValue* rhs) {
switch (type(base)) {
case KindOfUninit:
case KindOfNull:
detail::raiseEmptyObject();
not_reached();
case KindOfBoolean:
if (val(base).num) return SetOpPropNull(tvRef);
detail::raiseEmptyObject();
not_reached();
case KindOfInt64:
case KindOfDouble:
case KindOfPersistentVec:
case KindOfVec:
case KindOfPersistentDict:
case KindOfDict:
case KindOfPersistentKeyset:
case KindOfKeyset:
case KindOfResource:
case KindOfRFunc:
case KindOfFunc:
case KindOfClass:
case KindOfLazyClass:
case KindOfClsMeth:
case KindOfRClsMeth:
return SetOpPropNull(tvRef);
case KindOfPersistentString:
case KindOfString:
if (val(base).pstr->size()) return SetOpPropNull(tvRef);
detail::raiseEmptyObject();
not_reached();
case KindOfObject:
return SetOpPropObj(tvRef, ctx, op, val(base).pobj, key, rhs);
}
unknownBaseType(type(base));
}
inline TypedValue IncDecPropNull() {
raise_warning("Attempt to increment/decrement property of non-object");
return make_tv<KindOfNull>();
}
inline TypedValue IncDecPropObj(Class* ctx,
IncDecOp op,
ObjectData* base,
TypedValue key) {
auto keySD = prepareKey(key);
SCOPE_EXIT { decRefStr(keySD); };
return base->incDecProp(ctx, op, keySD);
}
inline TypedValue IncDecProp(
Class* ctx,
IncDecOp op,
TypedValue base,
TypedValue key
) {
switch (type(base)) {
case KindOfUninit:
case KindOfNull:
detail::raiseEmptyObject();
not_reached();
case KindOfBoolean:
if (val(base).num) return IncDecPropNull();
detail::raiseEmptyObject();
not_reached();
case KindOfInt64:
case KindOfDouble:
case KindOfPersistentVec:
case KindOfVec:
case KindOfPersistentDict:
case KindOfDict:
case KindOfPersistentKeyset:
case KindOfKeyset:
case KindOfResource:
case KindOfRFunc:
case KindOfFunc:
case KindOfClass:
case KindOfLazyClass:
case KindOfClsMeth:
case KindOfRClsMeth:
return IncDecPropNull();
case KindOfPersistentString:
case KindOfString:
if (val(base).pstr->size()) return IncDecPropNull();
detail::raiseEmptyObject();
not_reached();
case KindOfObject:
return IncDecPropObj(ctx, op, val(base).pobj, key);
}
unknownBaseType(type(base));
}
inline void UnsetPropObj(Class* ctx, ObjectData* instance, TypedValue key) {
// Prepare key.
auto keySD = prepareKey(key);
SCOPE_EXIT { decRefStr(keySD); };
// Unset property.
instance->unsetProp(ctx, keySD);
}
inline void UnsetProp(Class* ctx, TypedValue base, TypedValue key) {
// Validate base.
if (LIKELY(type(base) == KindOfObject)) {
UnsetPropObj(ctx, val(base).pobj, key);
}
}
///////////////////////////////////////////////////////////////////////////////
}