void VariableSerializer::serializeObjectImpl()

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();
    }
  }
}