hphp/runtime/ext/std/ext_std_variable.cpp (562 lines of code) (raw):
/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
| Copyright (c) 1997-2010 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#include "hphp/runtime/ext/std/ext_std_variable.h"
#include "hphp/runtime/base/array-init.h"
#include "hphp/runtime/base/backtrace.h"
#include "hphp/runtime/base/builtin-functions.h"
#include "hphp/runtime/base/collections.h"
#include "hphp/runtime/base/variable-serializer.h"
#include "hphp/runtime/base/variable-unserializer.h"
#include "hphp/runtime/base/zend-functions.h"
#include "hphp/runtime/vm/class-meth-data-ref.h"
#include "hphp/runtime/vm/jit/translator-inline.h"
#include "hphp/runtime/ext/collections/ext_collections-pair.h"
#include "hphp/runtime/ext/collections/ext_collections.h"
#include "hphp/runtime/server/http-protocol.h"
#include "hphp/util/hphp-config.h"
#include "hphp/util/logger.h"
#include <folly/Likely.h>
namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
const StaticString
s_unknown_type("unknown type"),
s_boolean("boolean"),
s_bool("bool"),
s_integer("integer"),
s_int("int"),
s_float("float"),
s_double("double"),
s_string("string"),
s_object("object"),
s_array("array"),
s_NULL("NULL"),
s_null("null"),
s_meth_caller_cls("__SystemLib\\MethCallerHelper"),
s_dyn_meth_caller_cls("__SystemLib\\DynMethCallerHelper");
String HHVM_FUNCTION(gettype, const Variant& v) {
if (v.getType() == KindOfResource && v.toCResRef().isInvalid()) {
return s_unknown_type;
}
/* Although getDataTypeString also handles the null type, it returns "null"
* (lower case). Unfortunately, PHP returns "NULL" (upper case) for
* gettype(). So we make an exception here. */
if (v.isNull()) {
return s_NULL;
}
if (RuntimeOption::EvalClassAsStringGetType &&
(v.isLazyClass() || v.isClass())) {
return s_string;
}
return getDataTypeString(v.getType());
}
String HHVM_FUNCTION(get_resource_type, const Resource& handle) {
return handle->o_getResourceName();
}
bool HHVM_FUNCTION(boolval, const Variant& v) {
return v.toBoolean();
}
int64_t HHVM_FUNCTION(intval, const Variant& v, int64_t base /* = 10 */) {
return v.toInt64(base);
}
double HHVM_FUNCTION(floatval, const Variant& v) {
return v.toDouble();
}
String HHVM_FUNCTION(strval, const Variant& v) {
return v.toString();
}
bool HHVM_FUNCTION(is_null, const Variant& v) {
return is_null(v.asTypedValue());
}
bool HHVM_FUNCTION(is_bool, const Variant& v) {
return is_bool(v.asTypedValue());
}
bool HHVM_FUNCTION(is_int, const Variant& v) {
return is_int(v.asTypedValue());
}
bool HHVM_FUNCTION(is_float, const Variant& v) {
return is_double(v.asTypedValue());
}
bool HHVM_FUNCTION(is_numeric, const Variant& v) {
return v.isNumeric(true);
}
bool HHVM_FUNCTION(is_string, const Variant& v) {
return is_string(v.asTypedValue());
}
bool HHVM_FUNCTION(is_scalar, const Variant& v) {
return v.isScalar();
}
bool HHVM_FUNCTION(HH_is_php_array, const Variant& v) {
return isArrayLikeType(v.getType()) && v.getArrayData()->isLegacyArray();
}
bool HHVM_FUNCTION(is_array, const Variant& v) {
if (RO::EvalLogOnIsArrayFunction) {
raise_notice("call to deprecated builtin is_array()");
}
return is_any_array(v.asTypedValue());
}
bool HHVM_FUNCTION(HH_is_vec, const Variant& v) {
return is_vec(v.asTypedValue());
}
bool HHVM_FUNCTION(HH_is_dict, const Variant& v) {
return is_dict(v.asTypedValue());
}
bool HHVM_FUNCTION(HH_is_keyset, const Variant& v) {
return is_keyset(v.asTypedValue());
}
bool HHVM_FUNCTION(HH_is_varray, const Variant& val) {
return is_vec(val.asTypedValue());
}
bool HHVM_FUNCTION(HH_is_darray, const Variant& val) {
return is_dict(val.asTypedValue());
}
bool HHVM_FUNCTION(HH_is_vec_or_varray, const Variant& val) {
return is_vec(val.asTypedValue());
}
bool HHVM_FUNCTION(HH_is_dict_or_darray, const Variant& val) {
return is_dict(val.asTypedValue());
}
bool HHVM_FUNCTION(HH_is_any_array, const Variant& val) {
return is_any_array(val.asTypedValue());
}
bool HHVM_FUNCTION(HH_is_list_like, const Variant& val) {
auto const& ty = val.getType();
if (!isArrayLikeType(ty)) return false;
if (isVecType(ty)) return true;
auto const& arr = val.asCArrRef();
return arr->isVectorData();
}
bool HHVM_FUNCTION(is_object, const Variant& v) {
return is_object(v.asTypedValue());
}
bool HHVM_FUNCTION(is_resource, const Variant& v) {
return (v.getType() == KindOfResource && !v.toCResRef().isInvalid());
}
bool HHVM_FUNCTION(HH_is_meth_caller, TypedValue v) {
if (tvIsFunc(v)) {
return val(v).pfunc->isMethCaller();
} else if (tvIsObject(v)) {
auto const mcCls = Class::lookup(s_meth_caller_cls.get());
auto const dynMcCls = Class::lookup(s_dyn_meth_caller_cls.get());
auto const cls = val(v).pobj->getVMClass();
assertx(mcCls);
assertx(dynMcCls);
return mcCls == cls || dynMcCls == cls;
}
return false;
}
///////////////////////////////////////////////////////////////////////////////
Array HHVM_FUNCTION(HH_object_prop_array,
const Object& obj,
bool ignore_late_init /* = false */) {
return obj.toArray(ignore_late_init).toDict();
}
///////////////////////////////////////////////////////////////////////////////
// input/output
ALWAYS_INLINE Variant print_r_impl(const Variant& expression,
bool ret /* = false */,
bool pure /* = false */) {
Variant res;
try {
VariableSerializer vs(VariableSerializer::Type::PrintR);
if (pure) {
vs.setPure();
}
if (ret) {
res = vs.serialize(expression, ret);
} else {
vs.serialize(expression, ret);
res = true;
}
} catch (StringBufferLimitException& e) {
raise_notice("print_r() exceeded max bytes limit");
res = e.m_result;
}
return res;
}
Variant HHVM_FUNCTION(print_r, const Variant& expression,
bool ret /* = false */) {
return print_r_impl(expression, ret, false);
}
Variant HHVM_FUNCTION(print_r_pure, const Variant& expression) {
return print_r_impl(expression, true, true);
}
ALWAYS_INLINE Variant var_export_impl(const Variant& expression,
bool ret /* = false */,
bool pure /* = false */) {
Variant res;
try {
VariableSerializer vs(VariableSerializer::Type::VarExport);
if (pure) {
vs.setPure();
}
if (ret) {
res = vs.serialize(expression, ret);
} else {
vs.serialize(expression, ret);
res = true;
}
} catch (StringBufferLimitException& e) {
raise_notice("var_export() exceeded max bytes limit");
}
return res;
}
Variant HHVM_FUNCTION(var_export, const Variant& expression,
bool ret /* = false */) {
return var_export_impl(expression, ret, false);
}
Variant HHVM_FUNCTION(var_export_pure, const Variant& expression) {
return var_export_impl(expression, true, true);
}
static ALWAYS_INLINE void do_var_dump(VariableSerializer& vs,
const Variant& expression) {
// manipulate maxCount to match PHP behavior
if (!expression.isObject()) {
vs.incMaxCount();
}
vs.serialize(expression, false);
}
void HHVM_FUNCTION(var_dump, const Variant& expression,
const Array& _argv /*=null_array */) {
VariableSerializer vs(VariableSerializer::Type::VarDump, 0, 2);
do_var_dump(vs, expression);
auto sz = _argv.size();
for (int i = 0; i < sz; i++) {
do_var_dump(vs, _argv[i]);
}
}
void HHVM_FUNCTION(debug_zval_dump, const Variant& variable) {
VariableSerializer vs(VariableSerializer::Type::DebugDump);
vs.serialize(variable, false);
}
/*
* Intrinsic for Containers, i.e. the subset of \HH\Traversable including
* 1. array:
* array, vec, dict, keyset
* 2. collection: Vector, Map, Set
* but not including Objects that implement e.g. \HH\Iterable or \HH\Iterator.
*/
Variant HHVM_FUNCTION(HH_first, const Variant& v) {
// 1. array, vec, dict, keyset
if (v.isArray() || v.isClsMeth()) {
auto arr = v.toArray();
if (arr->empty()) {
return init_null();
}
return arr->getValue(arr->iter_begin());
}
if (v.isObject()) {
auto obj = v.asCObjRef();
// 2. collection
if (obj->isCollection()) {
// Pair
if (obj->collectionType() == CollectionType::Pair) {
auto const pair = static_cast<c_Pair*>(obj.get());
return Variant::wrap(*pair->at(0));
}
// Vector, Map, Set, and Imm variants
auto arr = collections::asArray(obj.get());
if (arr->empty()) {
return init_null();
}
return arr->getValue(arr->iter_begin());
}
}
SystemLib::throwInvalidArgumentExceptionObject(
"Argument 1 passed to HH\\Lib\\_Private\\Native\\first() "
"must be a Container");
}
Variant HHVM_FUNCTION(HH_last, const Variant& v) {
// 1. array, vec, dict, keyset
if (v.isArray() || v.isClsMeth()) {
auto arr = v.toArray();
if (arr->empty()) {
return init_null();
}
return arr->getValue(arr->iter_last());
}
if (v.isObject()) {
auto obj = v.asCObjRef();
// 2. collection
if (obj->isCollection()) {
// Pair
if (obj->collectionType() == CollectionType::Pair) {
auto const pair = static_cast<c_Pair*>(obj.get());
return Variant::wrap(*pair->at(1));
}
// Vector, Map, Set, and Imm variants
auto arr = collections::asArray(obj.get());
if (arr->empty()) {
return init_null();
}
return arr->getValue(arr->iter_last());
}
}
SystemLib::throwInvalidArgumentExceptionObject(
"Argument 1 passed to HH\\Lib\\_Private\\Native\\last() "
"must be a Container");
}
Variant HHVM_FUNCTION(HH_first_key, const Variant& v) {
// 1. array, vec, dict, keyset
if (v.isArray() || v.isClsMeth()) {
auto arr = v.toArray();
if (arr->empty()) {
return init_null();
}
return arr->getKey(arr->iter_begin());
}
if (v.isObject()) {
auto obj = v.asCObjRef();
// 2. collection
if (obj->isCollection()) {
// Pair
if (obj->collectionType() == CollectionType::Pair) {
return Variant::wrap(make_tv<KindOfInt64>(0));
}
// Vector, Map, Set, and Imm variants
auto arr = collections::asArray(obj.get());
if (arr->empty()) {
return init_null();
}
return arr->getKey(arr->iter_begin());
}
}
SystemLib::throwInvalidArgumentExceptionObject(
"Argument 1 passed to HH\\Lib\\_Private\\Native\\first_key() "
"must be a Container");
}
Variant HHVM_FUNCTION(HH_last_key, const Variant& v) {
// 1. array, vec, dict, keyset
if (v.isArray() || v.isClsMeth()) {
auto arr = v.toArray();
if (arr->empty()) {
return init_null();
}
return arr->getKey(arr->iter_last());
}
if (v.isObject()) {
auto obj = v.asCObjRef();
// 2. collection
if (obj->isCollection()) {
// Pair
if (obj->collectionType() == CollectionType::Pair) {
return Variant::wrap(make_tv<KindOfInt64>(1));
}
// Vector, Map, Set, and Imm variants
auto arr = collections::asArray(obj.get());
if (arr->empty()) {
return init_null();
}
return arr->getKey(arr->iter_last());
}
}
SystemLib::throwInvalidArgumentExceptionObject(
"Argument 1 passed to HH\\Lib\\_Private\\Native\\last_key() "
"must be a Container");
}
namespace {
const StaticString s_Res("i:0;");
struct SerializeOptions {
bool keepDVArrays = false;
bool forcePHPArrays = false;
bool warnOnHackArrays = false;
bool warnOnPHPArrays = false;
bool ignoreLateInit = false;
bool serializeProvenanceAndLegacy = false;
bool disallowObjects = false;
};
ALWAYS_INLINE String serialize_impl(const Variant& value,
const SerializeOptions& opts,
bool pure) {
switch (value.getType()) {
case KindOfClass:
case KindOfLazyClass:
case KindOfPersistentString:
case KindOfString: {
auto const str =
isStringType(value.getType()) ? value.getStringData() :
isClassType(value.getType()) ? classToStringHelper(value.toClassVal()) :
lazyClassToStringHelper(value.toLazyClassVal());
auto const size = str->size();
if (size >= RuntimeOption::MaxSerializedStringSize) {
throw Exception("Size of serialized string (%ld) exceeds max", size);
}
StringBuffer sb;
sb.append("s:");
sb.append(size);
sb.append(":\"");
sb.append(str->data(), size);
sb.append("\";");
return sb.detach();
}
case KindOfResource:
return s_Res;
case KindOfUninit:
case KindOfNull:
case KindOfBoolean:
case KindOfInt64:
case KindOfFunc:
case KindOfPersistentVec:
case KindOfVec:
case KindOfPersistentDict:
case KindOfDict:
case KindOfPersistentKeyset:
case KindOfKeyset:
case KindOfDouble:
case KindOfObject:
case KindOfClsMeth:
case KindOfRClsMeth:
case KindOfRFunc:
break;
}
VariableSerializer vs(VariableSerializer::Type::Serialize);
if (opts.keepDVArrays) vs.keepDVArrays();
if (opts.forcePHPArrays) vs.setForcePHPArrays();
if (opts.warnOnHackArrays) vs.setHackWarn();
if (opts.warnOnPHPArrays) vs.setPHPWarn();
if (opts.ignoreLateInit) vs.setIgnoreLateInit();
if (opts.serializeProvenanceAndLegacy) vs.setSerializeProvenanceAndLegacy();
if (opts.disallowObjects) vs.setDisallowObjects();
if (pure) vs.setPure();
// Keep the count so recursive calls to serialize() embed references properly.
return vs.serialize(value, true, true);
}
}
String HHVM_FUNCTION(serialize, const Variant& value) {
return serialize_impl(value, SerializeOptions(), false);
}
String HHVM_FUNCTION(serialize_pure, const Variant& value) {
return serialize_impl(value, SerializeOptions(), true);
}
const StaticString
s_forcePHPArrays("forcePHPArrays"),
s_keepDVArrays("keepDVArrays"),
s_warnOnHackArrays("warnOnHackArrays"),
s_warnOnPHPArrays("warnOnPHPArrays"),
s_ignoreLateInit("ignoreLateInit"),
s_disallowObjects("disallowObjects"),
s_serializeProvenanceAndLegacy("serializeProvenanceAndLegacy");
String HHVM_FUNCTION(HH_serialize_with_options,
const Variant& value, const Array& options) {
SerializeOptions opts;
opts.keepDVArrays = options.exists(s_keepDVArrays) &&
options[s_keepDVArrays].toBoolean();
opts.forcePHPArrays = options.exists(s_forcePHPArrays) &&
options[s_forcePHPArrays].toBoolean();
opts.warnOnHackArrays = options.exists(s_warnOnHackArrays) &&
options[s_warnOnHackArrays].toBoolean();
opts.warnOnPHPArrays = options.exists(s_warnOnPHPArrays) &&
options[s_warnOnPHPArrays].toBoolean();
opts.ignoreLateInit = options.exists(s_ignoreLateInit) &&
options[s_ignoreLateInit].toBoolean();
opts.serializeProvenanceAndLegacy =
options.exists(s_serializeProvenanceAndLegacy) &&
options[s_serializeProvenanceAndLegacy].toBoolean();
opts.disallowObjects = options.exists(s_disallowObjects) &&
options[s_disallowObjects].toBoolean();
return serialize_impl(value, opts, false);
}
String serialize_keep_dvarrays(const Variant& value) {
SerializeOptions opts;
opts.keepDVArrays = true;
return serialize_impl(value, opts, false);
}
String HHVM_FUNCTION(hhvm_intrinsics_serialize_keep_dvarrays,
const Variant& value) {
SerializeOptions opts;
opts.keepDVArrays = true;
return serialize_impl(value, opts, false);
}
Variant HHVM_FUNCTION(unserialize, const String& str,
const Array& options) {
return unserialize_from_string(
str,
VariableUnserializer::Type::Serialize,
options
);
}
Variant HHVM_FUNCTION(unserialize_pure, const String& str,
const Array& options) {
return unserialize_from_string(
str,
VariableUnserializer::Type::Serialize,
options,
true
);
}
///////////////////////////////////////////////////////////////////////////////
// variable table
void HHVM_FUNCTION(parse_str,
const String& str,
Array& arr) {
arr = Array::CreateDict();
HttpProtocol::DecodeParameters(arr, str.data(), str.size());
}
/////////////////////////////////////////////////////////////////////////////
bool HHVM_FUNCTION(HH_is_late_init_prop_init,
const Object& obj,
const String& name) {
auto const ctx = fromCaller(
[] (const BTFrame& frm) { return frm.func()->cls(); }
);
auto const val = obj->getPropIgnoreLateInit(ctx, name.get());
if (!val) {
SystemLib::throwInvalidArgumentExceptionObject(
folly::sformat(
"Unknown or inaccessible property '{}' on object of class {}",
name.get(),
obj->getVMClass()->name()
)
);
}
return type(val) != KindOfUninit;
}
bool HHVM_FUNCTION(HH_is_late_init_sprop_init,
const String& clsName,
const String& name) {
auto const cls = Class::load(clsName.get());
if (!cls) {
SystemLib::throwInvalidArgumentExceptionObject(
folly::sformat("Unknown class {}", clsName)
);
}
auto const ctx = fromCaller(
[] (const BTFrame& frm) { return frm.func()->cls(); }
);
auto const lookup = cls->getSPropIgnoreLateInit(ctx, name.get());
if (!lookup.val || !lookup.accessible) {
SystemLib::throwInvalidArgumentExceptionObject(
folly::sformat(
"Unknown or inaccessible static property '{}' on class {}",
name.get(),
clsName.get()
)
);
}
return type(lookup.val) != KindOfUninit;
}
bool HHVM_FUNCTION(HH_global_key_exists, StringArg key) {
return g_context->m_globalNVTable->lookup(key.get()) != nullptr;
}
/////////////////////////////////////////////////////////////////////////////
void StandardExtension::initVariable() {
HHVM_FE(is_null);
HHVM_FE(is_bool);
HHVM_FE(is_int);
HHVM_FALIAS(is_integer, is_int);
HHVM_FALIAS(is_long, is_int);
HHVM_FE(is_float);
HHVM_FALIAS(is_double, is_float);
HHVM_FALIAS(is_real, is_float);
HHVM_FE(is_numeric);
HHVM_FE(is_string);
HHVM_FE(is_scalar);
HHVM_FE(is_array);
HHVM_FALIAS(HH\\is_vec, HH_is_vec);
HHVM_FALIAS(HH\\is_dict, HH_is_dict);
HHVM_FALIAS(HH\\is_keyset, HH_is_keyset);
HHVM_FALIAS(HH\\is_varray, HH_is_varray);
HHVM_FALIAS(HH\\is_darray, HH_is_darray);
HHVM_FALIAS(HH\\is_vec_or_varray, HH_is_vec_or_varray);
HHVM_FALIAS(HH\\is_dict_or_darray, HH_is_dict_or_darray);
HHVM_FALIAS(HH\\is_any_array, HH_is_any_array);
HHVM_FALIAS(HH\\is_php_array, HH_is_php_array);
HHVM_FALIAS(HH\\is_list_like, HH_is_list_like);
HHVM_FALIAS(HH\\is_meth_caller, HH_is_meth_caller);
HHVM_FE(is_object);
HHVM_FE(is_resource);
HHVM_FE(boolval);
HHVM_FE(intval);
HHVM_FE(floatval);
HHVM_FALIAS(doubleval, floatval);
HHVM_FE(strval);
HHVM_FE(gettype);
HHVM_FE(get_resource_type);
HHVM_FE(print_r);
HHVM_FE(print_r_pure);
HHVM_FE(var_export);
HHVM_FE(var_export_pure);
HHVM_FE(debug_zval_dump);
HHVM_FE(var_dump);
HHVM_FE(serialize);
HHVM_FE(serialize_pure);
HHVM_FE(unserialize);
HHVM_FE(unserialize_pure);
HHVM_FE(parse_str);
HHVM_FALIAS(HH\\object_prop_array, HH_object_prop_array);
HHVM_FALIAS(HH\\serialize_with_options, HH_serialize_with_options);
HHVM_FALIAS(HH\\Lib\\_Private\\Native\\first, HH_first);
HHVM_FALIAS(HH\\Lib\\_Private\\Native\\last, HH_last);
HHVM_FALIAS(HH\\Lib\\_Private\\Native\\first_key, HH_first_key);
HHVM_FALIAS(HH\\Lib\\_Private\\Native\\last_key, HH_last_key);
HHVM_FALIAS(HH\\is_late_init_prop_init, HH_is_late_init_prop_init);
HHVM_FALIAS(HH\\is_late_init_sprop_init, HH_is_late_init_sprop_init);
HHVM_FALIAS(HH\\global_key_exists, HH_global_key_exists);
if (RuntimeOption::EnableIntrinsicsExtension) {
HHVM_FALIAS(__hhvm_intrinsics\\serialize_keep_dvarrays,
hhvm_intrinsics_serialize_keep_dvarrays);
}
loadSystemlib("std_variable");
}
///////////////////////////////////////////////////////////////////////////////
} // namespace HPHP