in hphp/runtime/base/variable-serializer.cpp [2097:2367]
void VariableSerializer::serializeObjectImpl(const ObjectData* obj) {
bool handleSleep = false;
Variant serializableNativeData = init_null();
Variant ret;
auto const type = getType();
if (obj->isCollection()) {
serializeCollection(const_cast<ObjectData*>(obj));
return;
}
if (RO::EvalForbidMethCallerHelperSerialize &&
(type == Type::Serialize || type == Type::Internal ||
type == Type::DebuggerSerialize || type == Type::JSON) &&
obj->getVMClass() == SystemLib::s_MethCallerHelperClass) {
if (RO::EvalForbidMethCallerHelperSerialize == 1) {
raise_warning("Serializing MethCallerHelper");
} else {
SystemLib::throwInvalidOperationExceptionObject(
VarNR{s_invalidMethCallerSerde.get()}
);
}
}
if (m_disallowObjects) {
SystemLib::throwInvalidOperationExceptionObject(
VarNR{s_disallowedObjectSerde.get()}
);
}
if (LIKELY(type == VariableSerializer::Type::Serialize ||
type == VariableSerializer::Type::Internal ||
type == VariableSerializer::Type::APCSerialize)) {
if (obj->instanceof(SystemLib::s_SerializableClass)) {
assertx(!obj->isCollection());
ret =
const_cast<ObjectData*>(obj)->o_invoke_few_args(s_serialize, RuntimeCoeffects::fixme(), 0);
if (ret.isString()) {
writeSerializableObject(obj->getClassName(), ret.toString());
} else if (ret.isNull()) {
writeNull();
} else {
raise_error("%s::serialize() must return a string or NULL",
obj->getClassName().data());
}
return;
}
// Only serialize CPP extension type instances which can actually
// be deserialized. Otherwise, raise a warning and serialize
// null.
// Similarly, do not try to serialize WaitHandles
// as they contain internal state via non-NativeData means.
auto cls = obj->getVMClass();
if ((cls->instanceCtor() && !cls->isCppSerializable()) ||
obj->isWaitHandle()) {
raise_warning("Attempted to serialize unserializable builtin class %s",
obj->getVMClass()->preClass()->name()->data());
serializeVariant(init_null().asTypedValue());
return;
}
if (type == VariableSerializer::Type::APCSerialize) {
if (cls == SystemLib::s_MethCallerHelperClass) {
if (RO::EvalForbidMethCallerAPCSerialize == 1) {
raise_warning("Storing meth_caller in APC");
} else if (RO::EvalForbidMethCallerAPCSerialize > 1) {
SystemLib::throwInvalidOperationExceptionObject(
VarNR{s_invalidMethCallerAPC.get()}
);
}
} else if (cls == SystemLib::s_DynMethCallerHelperClass) {
SystemLib::throwInvalidOperationExceptionObject(
VarNR{s_invalidMethCallerAPC.get()}
);
}
}
if (obj->getVMClass()->rtAttribute(Class::HasSleep)) {
handleSleep = true;
auto const providedCoeffects =
m_pure ? RuntimeCoeffects::pure() : RuntimeCoeffects::defaults();
ret = const_cast<ObjectData*>(obj)->invokeSleep(providedCoeffects);
}
if (obj->hasNativeData()) {
auto* ndi = cls->getNativeDataInfo();
if (ndi->isSerializable()) {
serializableNativeData = Native::nativeDataSleep(obj);
}
}
} else if (UNLIKELY(type == VariableSerializer::Type::DebuggerSerialize)) {
// Don't try to serialize a CPP extension class which doesn't
// support serialization. Just send the class name instead.
if (obj->isCppBuiltin() && !obj->getVMClass()->isCppSerializable()) {
write(obj->getClassName());
return;
}
if (obj->hasNativeData() &&
obj->getVMClass()->getNativeDataInfo()->isSerializable()) {
serializableNativeData = Native::nativeDataSleep(obj);
}
}
if (UNLIKELY(handleSleep)) {
assertx(!obj->isCollection());
if (ret.isArray()) {
Array wanted = Array::CreateDict();
assertx(isArrayLikeType(ret.getType()));
const Array &props = ret.asCArrRef();
for (ArrayIter iter(props); iter; ++iter) {
String memberName = iter.second().toString();
String propName = memberName;
auto obj_cls = obj->getVMClass();
Class* ctx = obj_cls;
auto attrMask = AttrNone;
if (memberName.data()[0] == 0) {
int subLen = memberName.find('\0', 1) + 1;
if (subLen > 2) {
if (subLen == 3 && memberName.data()[1] == '*') {
attrMask = AttrProtected;
memberName = memberName.substr(subLen);
} else {
attrMask = AttrPrivate;
String cls = memberName.substr(1, subLen - 2);
ctx = Class::lookup(cls.get());
if (ctx) {
memberName = memberName.substr(subLen);
} else {
ctx = obj_cls;
}
}
}
}
auto const lookup = obj_cls->getDeclPropSlot(ctx, memberName.get());
auto const slot = lookup.slot;
if (slot != kInvalidSlot && lookup.accessible) {
auto propVal = const_cast<ObjectData*>(obj)->propLvalAtOffset(slot);
auto const& prop = obj_cls->declProperties()[slot];
if (propVal.type() != KindOfUninit) {
if (prop.attrs & AttrPrivate) {
memberName = concat4(s_zero, ctx->nameStr(),
s_zero, memberName);
} else if (prop.attrs & AttrProtected) {
memberName = concat(s_protected_prefix, memberName);
}
if (!attrMask || (attrMask & prop.attrs) == attrMask) {
wanted.set(memberName, propVal.tv());
continue;
}
} else if (prop.attrs & AttrLateInit) {
if (m_ignoreLateInit) {
continue;
} else {
throw_late_init_prop(prop.cls, memberName.get(), false);
}
}
}
if (!attrMask &&
UNLIKELY(obj->getAttribute(ObjectData::HasDynPropArr))) {
auto const prop = obj->dynPropArray()->get(propName.get());
if (prop.is_init()) {
wanted.set(propName, prop);
continue;
}
}
raise_notice("serialize(): \"%s\" returned as member variable from "
"__sleep() but does not exist", propName.data());
wanted.set(propName, init_null());
}
pushObjectInfo(obj->getClassName(), 'O');
if (!serializableNativeData.isNull()) {
wanted.set(s_serializedNativeDataKey, serializableNativeData);
}
serializeObjProps(wanted);
popObjectInfo();
} else {
raise_notice("serialize(): __sleep should return an array only "
"containing the names of instance-variables to "
"serialize");
serializeVariant(uninit_null().asTypedValue());
}
} else {
if (type == VariableSerializer::Type::VarExport &&
obj->instanceof(c_Closure::classof())) {
write(obj->getClassName());
} else {
auto className = obj->getClassName();
Array properties = getSerializeProps(obj);
if (type == VariableSerializer::Type::DebuggerSerialize) {
try {
CoeffectsAutoGuard _;
auto val = const_cast<ObjectData*>(obj)->invokeToDebugDisplay(
RuntimeCoeffects::automatic());
if (val.isInitialized()) {
properties.set(s_PHP_DebugDisplay, *val.asTypedValue());
}
} catch (const Object &e) {
assertx(e->instanceof(SystemLib::s_ErrorClass) ||
e->instanceof(SystemLib::s_ExceptionClass));
assertx(
SystemLib::s_ErrorClass->lookupDeclProp(s_message.get()) == 0 &&
SystemLib::s_ExceptionClass->lookupDeclProp(s_message.get()) == 0
);
auto const message_rval = e->propRvalAtOffset(Slot{0});
if (isStringType(message_rval.type())) {
raise_warning("%s::__toDebugDisplay() threw PHP exception "
"of class %s with message '%s'",
obj->getClassName().data(), e->getClassName().data(),
message_rval.val().pstr->data());
} else {
raise_warning("%s::__toDebugDisplay() threw PHP exception "
"of class %s with non-string message",
obj->getClassName().data(), e->getClassName().data());
}
} catch (const std::exception &e) {
raise_warning("%s::__toDebugDisplay() threw C++ exception: %s",
obj->getClassName().data(), e.what());
} catch (...) {
raise_warning("%s::__toDebugDisplay() threw unknown exception",
obj->getClassName().data());
}
}
if (type == VariableSerializer::Type::DebuggerDump) {
// Expect to display as their stringified classname.
if (obj->instanceof(c_Closure::classof())) {
write(obj->getVMClass()->nameStr());
return;
}
// If we have a DebugDisplay prop saved, use it.
auto const debugDisp = obj->getProp(nullptr, s_PHP_DebugDisplay.get());
if (debugDisp) {
serializeVariant(debugDisp, false, false, true);
return;
}
// Otherwise compute it if we have a __toDebugDisplay method.
CoeffectsAutoGuard _;
auto val = const_cast<ObjectData*>(obj)->invokeToDebugDisplay(
RuntimeCoeffects::automatic());
if (val.isInitialized()) {
serializeVariant(val.asTypedValue(), false, false, true);
return;
}
}
if (className.get() == s_PHP_Incomplete_Class.get() &&
(type == VariableSerializer::Type::Serialize ||
type == VariableSerializer::Type::Internal ||
type == VariableSerializer::Type::APCSerialize ||
type == VariableSerializer::Type::DebuggerSerialize ||
type == VariableSerializer::Type::DebuggerDump)) {
auto const cname = obj->getProp(
nullptr,
s_PHP_Incomplete_Class_Name.get()
);
if (cname && isStringType(cname.type())) {
pushObjectInfo(StrNR(cname.val().pstr), 'O');
properties.remove(s_PHP_Incomplete_Class_Name, true);
serializeObjProps(properties);
popObjectInfo();
return;
}
}
pushObjectInfo(className, 'O');
if (!serializableNativeData.isNull()) {
properties.set(s_serializedNativeDataKey, serializableNativeData);
}
serializeObjProps(properties);
popObjectInfo();
}
}
}