2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/base/variable-serializer.h"
18 #include "hphp/runtime/base/backtrace.h"
19 #include "hphp/runtime/base/execution-context.h"
20 #include "hphp/runtime/base/collections.h"
21 #include "hphp/runtime/base/comparisons.h"
22 #include "hphp/runtime/base/tv-refcount.h"
23 #include "hphp/util/exception.h"
24 #include "hphp/runtime/base/zend-printf.h"
25 #include "hphp/runtime/base/zend-functions.h"
26 #include "hphp/runtime/base/zend-string.h"
28 #include "hphp/runtime/base/runtime-option.h"
29 #include "hphp/runtime/base/array-iterator.h"
30 #include "hphp/runtime/base/mixed-array.h"
31 #include "hphp/runtime/base/mixed-array-defs.h"
32 #include "hphp/runtime/base/packed-array-defs.h"
33 #include "hphp/runtime/base/request-local.h"
34 #include "hphp/runtime/base/utf8-decode.h"
35 #include "hphp/runtime/ext/collections/ext_collections.h"
36 #include "hphp/runtime/ext/json/JSON_parser.h"
37 #include "hphp/runtime/ext/json/ext_json.h"
38 #include "hphp/runtime/ext/std/ext_std_closure.h"
39 #include "hphp/runtime/vm/native-data.h"
42 ///////////////////////////////////////////////////////////////////////////////
44 extern const StaticString
45 s_serializedNativeDataKey(std::string("\0native", 7));
48 s_JsonSerializable("JsonSerializable"),
49 s_jsonSerialize("jsonSerialize"),
50 s_serialize("serialize"),
52 s_protected_prefix("\0*\0", 3),
53 s_PHP_DebugDisplay("__PHP_DebugDisplay"),
54 s_PHP_Incomplete_Class("__PHP_Incomplete_Class"),
55 s_PHP_Incomplete_Class_Name("__PHP_Incomplete_Class_Name"),
56 s_debugInfo("__debugInfo");
58 [[noreturn
]] NEVER_INLINE
59 static void throwNestingException() {
60 throw ExtendedException("Nesting level too deep - recursive dependency?");
63 ///////////////////////////////////////////////////////////////////////////////
65 VariableSerializer::SavedRefMap::~SavedRefMap() {
66 for (auto& i
: m_mapping
) {
67 tvDecRefGen(const_cast<TypedValue
*>(&i
.first
));
71 VariableSerializer::~VariableSerializer() {
74 VariableSerializer::VariableSerializer(Type type
, int option
/* = 0 */,
75 int maxRecur
/* = 3 */)
82 , m_keepDVArrays
{type
!= Type::Serialize
}
87 , m_maxCount(maxRecur
)
92 if (type
== Type::DebuggerSerialize
) {
93 m_maxLevelDebugger
= g_context
->debuggerSettings
.printLevel
;
97 VariableSerializer::ArrayKind
98 VariableSerializer::getKind(const ArrayData
* arr
) const {
99 if (arr
->isDict()) return VariableSerializer::ArrayKind::Dict
;
100 if (arr
->isVecArray()) return VariableSerializer::ArrayKind::Vec
;
101 if (arr
->isKeyset()) return VariableSerializer::ArrayKind::Keyset
;
102 assert(arr
->isPHPArray());
103 if (m_keepDVArrays
) {
104 if (arr
->isVArray()) return VariableSerializer::ArrayKind::VArray
;
105 if (arr
->isDArray()) return VariableSerializer::ArrayKind::DArray
;
107 return VariableSerializer::ArrayKind::PHP
;
110 void VariableSerializer::pushObjectInfo(const String
& objClass
, int objId
,
112 assert(objCode
== 'O' || objCode
== 'V' || objCode
== 'K');
113 m_objectInfos
.emplace_back(
114 ObjectInfo
{ m_objClass
, m_objId
, m_objCode
, m_rsrcName
, m_rsrcId
}
116 m_objClass
= objClass
;
123 void VariableSerializer::pushResourceInfo(const String
& rsrcName
, int rsrcId
) {
124 m_objectInfos
.emplace_back(
125 ObjectInfo
{ m_objClass
, m_objId
, m_objCode
, m_rsrcName
, m_rsrcId
}
130 m_rsrcName
= rsrcName
;
134 void VariableSerializer::popObjectInfo() {
135 ObjectInfo
&info
= m_objectInfos
.back();
136 m_objClass
= info
.objClass
;
137 m_objId
= info
.objId
;
138 m_objCode
= info
.objCode
;
139 m_rsrcName
= info
.rsrcName
;
140 m_rsrcId
= info
.rsrcId
;
141 m_objectInfos
.pop_back();
144 __thread
int64_t VariableSerializer::serializationSizeLimit
=
147 void VariableSerializer::popResourceInfo() {
151 String
VariableSerializer::serialize(const Variant
& v
, bool ret
,
152 bool keepCount
/* = false */) {
156 buf
.setOutputLimit(serializationSizeLimit
);
158 buf
.setOutputLimit(StringData::MaxSize
);
160 m_valueCount
= keepCount
? m_valueCount
+ 1 : 1;
163 return m_buf
->detach();
165 String str
= m_buf
->detach();
166 g_context
->write(str
);
171 String
VariableSerializer::serializeValue(const Variant
& v
, bool limit
) {
175 buf
.setOutputLimit(serializationSizeLimit
);
179 return m_buf
->detach();
182 String
VariableSerializer::serializeWithLimit(const Variant
& v
, int limit
) {
183 if (m_type
== Type::Serialize
|| m_type
== Type::Internal
||
184 m_type
== Type::JSON
|| m_type
== Type::APCSerialize
||
185 m_type
== Type::DebuggerSerialize
) {
191 if (serializationSizeLimit
> 0 &&
192 (limit
<= 0 || limit
> serializationSizeLimit
)) {
193 limit
= serializationSizeLimit
;
195 buf
.setOutputLimit(limit
);
196 //Does not need m_valueCount, which is only useful with the unsupported types
199 } catch (StringBufferLimitException
&e
) {
202 return m_buf
->detach();
205 ///////////////////////////////////////////////////////////////////////////////
207 void VariableSerializer::write(bool v
) {
210 if (v
) m_buf
->append(1);
212 case Type::VarExport
:
213 case Type::PHPOutput
:
215 case Type::DebuggerDump
:
216 m_buf
->append(v
? "true" : "false");
219 case Type::DebugDump
:
221 m_buf
->append(v
? "bool(true)" : "bool(false)");
225 case Type::Serialize
:
227 case Type::APCSerialize
:
228 case Type::DebuggerSerialize
:
229 m_buf
->append(v
? "b:1;" : "b:0;");
237 void VariableSerializer::write(int64_t v
) {
240 case Type::VarExport
:
241 case Type::PHPOutput
:
243 case Type::DebuggerDump
:
248 m_buf
->append("int(");
250 m_buf
->append(")\n");
252 case Type::DebugDump
:
254 m_buf
->append("long(");
260 case Type::Serialize
:
262 case Type::APCSerialize
:
263 case Type::DebuggerSerialize
:
274 void VariableSerializer::write(double v
) {
275 auto const precision
= 14;
276 auto const serde_precision
= 17;
280 if (!std::isinf(v
) && !std::isnan(v
)) {
282 vspprintf(&buf
, 0, "%.*k", precision
, v
);
284 if (m_option
& k_JSON_PRESERVE_ZERO_FRACTION
285 && strchr(buf
, '.') == nullptr) {
290 json_set_last_error_code(json_error_codes::JSON_ERROR_INF_OR_NAN
);
295 case Type::VarExport
:
296 case Type::PHPOutput
:
298 case Type::DebuggerDump
:
301 bool isExport
= m_type
== Type::VarExport
|| m_type
== Type::PHPOutput
;
302 vspprintf(&buf
, 0, isExport
? "%.*H" : "%.*G", precision
, v
);
304 // In PHPOutput mode, we always want doubles to parse as
305 // doubles, so make sure there's a decimal point.
306 if (m_type
== Type::PHPOutput
&& strpbrk(buf
, ".E") == nullptr) {
313 case Type::DebugDump
:
316 vspprintf(&buf
, 0, "float(%.*G)", precision
, v
);
324 case Type::Serialize
:
326 case Type::APCSerialize
:
327 case Type::DebuggerSerialize
:
330 m_buf
->append("NAN");
331 } else if (std::isinf(v
)) {
332 if (v
< 0) m_buf
->append('-');
333 m_buf
->append("INF");
336 vspprintf(&buf
, 0, "%.*H", serde_precision
, v
);
348 uint16_t reverse16(uint16_t us
) {
350 ((us
& 0xf) << 12) | (((us
>> 4) & 0xf) << 8) |
351 (((us
>> 8) & 0xf) << 4) | ((us
>> 12) & 0xf);
354 // Potentially need to escape all control characters (< 32) and also "\/<>&'@%
355 static const bool jsonNoEscape
[128] = {
356 false, false, false, false, false, false, false, false,
357 false, false, false, false, false, false, false, false,
358 false, false, false, false, false, false, false, false,
359 false, false, false, false, false, false, false, false,
360 true, true, false, true, true, false, false, false,
361 true, true, true, true, true, true, true, false,
362 true, true, true, true, true, true, true, true,
363 true, true, true, true, false, true, false, true,
364 false, true, true, true, true, true, true, true,
365 true, true, true, true, true, true, true, true,
366 true, true, true, true, true, true, true, true,
367 true, true, true, true, false, true, true, true,
368 true, true, true, true, true, true, true, true,
369 true, true, true, true, true, true, true, true,
370 true, true, true, true, true, true, true, true,
371 true, true, true, true, true, true, true, true,
374 static void appendJsonEscape(StringBuffer
& sb
,
379 sb
.append("\"\"", 2);
383 static const char digits
[] = "0123456789abcdef";
385 auto const start
= sb
.size();
388 // Do a fast path for ASCII characters that don't need escaping
392 if (UNLIKELY((unsigned char)c
>= 128 || !jsonNoEscape
[c
])) {
402 UTF8To16Decoder
decoder(s
+ pos
, len
- pos
, options
& k_JSON_FB_LOOSE
);
404 int c
= options
& k_JSON_UNESCAPED_UNICODE
? decoder
.decodeAsUTF8()
410 if (c
== UTF8_ERROR
) {
411 json_set_last_error_code(json_error_codes::JSON_ERROR_UTF8
);
412 // discard the part that has been already decoded.
414 sb
.append("null", 4);
418 unsigned short us
= (unsigned short)c
;
421 if (options
& k_JSON_HEX_QUOT
) {
422 sb
.append("\\u0022", 6);
424 sb
.append("\\\"", 2);
427 case '\\': sb
.append("\\\\", 2); break;
429 if (options
& k_JSON_UNESCAPED_SLASHES
) {
435 case '\b': sb
.append("\\b", 2); break;
436 case '\f': sb
.append("\\f", 2); break;
437 case '\n': sb
.append("\\n", 2); break;
438 case '\r': sb
.append("\\r", 2); break;
439 case '\t': sb
.append("\\t", 2); break;
441 if (options
& k_JSON_HEX_TAG
|| options
& k_JSON_FB_EXTRA_ESCAPES
) {
442 sb
.append("\\u003C", 6);
448 if (options
& k_JSON_HEX_TAG
) {
449 sb
.append("\\u003E", 6);
455 if (options
& k_JSON_HEX_AMP
) {
456 sb
.append("\\u0026", 6);
462 if (options
& k_JSON_HEX_APOS
) {
463 sb
.append("\\u0027", 6);
469 if (options
& k_JSON_FB_EXTRA_ESCAPES
) {
470 sb
.append("\\u0040", 6);
476 if (options
& k_JSON_FB_EXTRA_ESCAPES
) {
477 sb
.append("\\u0025", 6);
484 ((options
& k_JSON_UNESCAPED_UNICODE
) || (us
& 127) == us
)) {
489 sb
.append(digits
[us
& ((1 << 4) - 1)]); us
>>= 4;
490 sb
.append(digits
[us
& ((1 << 4) - 1)]); us
>>= 4;
491 sb
.append(digits
[us
& ((1 << 4) - 1)]); us
>>= 4;
492 sb
.append(digits
[us
& ((1 << 4) - 1)]);
499 void VariableSerializer::write(const char *v
, int len
/* = -1 */,
500 bool isArrayKey
/* = false */,
501 bool noQuotes
/* = false */) {
502 if (v
== nullptr) v
= "";
503 if (len
< 0) len
= strlen(v
);
507 m_buf
->append(v
, len
);
510 case Type::VarExport
: {
513 for (int i
= 0; i
< len
; i
++, p
++) {
515 // adapted from Zend php_var_export and php_addcslashes
517 m_buf
->append("' . \"\\0\" . '");
519 } else if (c
== '\'' || c
== '\\') {
528 case Type::DebugDump
: {
530 m_buf
->append("string(");
532 m_buf
->append(") \"");
533 m_buf
->append(v
, len
);
539 case Type::Serialize
:
541 case Type::APCSerialize
:
542 case Type::DebuggerSerialize
:
545 m_buf
->append(":\"");
546 m_buf
->append(v
, len
);
547 m_buf
->append("\";");
550 if ((m_option
& k_JSON_NUMERIC_CHECK
) && !isArrayKey
) {
551 int64_t lval
; double dval
;
552 auto dt
= is_numeric_string(v
, len
, &lval
, &dval
, 0);
556 } else if (isDoubleType(dt
)) {
561 appendJsonEscape(*m_buf
, v
, len
, m_option
);
564 case Type::DebuggerDump
:
565 case Type::PHPOutput
: {
568 for (int i
= 0; i
< len
; ++i
) {
569 const unsigned char c
= v
[i
];
571 case '\n': m_buf
->append("\\n"); break;
572 case '\r': m_buf
->append("\\r"); break;
573 case '\t': m_buf
->append("\\t"); break;
574 case '\\': m_buf
->append("\\\\"); break;
575 case '$': m_buf
->append("\\$"); break;
576 case '"': m_buf
->append("\\\""); break;
578 if (c
>= ' ' && c
<= '~') {
579 // The range [' ', '~'] contains only printable characters
580 // and we've already handled special cases above
584 snprintf(buf
, sizeof(buf
), "\\%03o", c
);
600 void VariableSerializer::write(const String
& v
) {
601 if (m_type
== Type::APCSerialize
&& !v
.isNull() && v
.get()->isStatic()) {
608 m_buf
->append(u
.buf
, 8);
615 void VariableSerializer::write(const Object
& v
) {
616 if (!v
.isNull() && m_type
== Type::JSON
) {
618 if (v
.instanceof(s_JsonSerializable
)) {
619 assert(!v
->isCollection());
620 Variant ret
= v
->o_invoke_few_args(s_jsonSerialize
, 0);
621 // for non objects or when $this is not returned
622 if (!ret
.isObject() || (ret
.isObject() && !same(ret
, v
))) {
623 if (ret
.isArray() || ret
.isObject()) {
624 preventOverflow(v
, [&ret
, this]() {
628 // Don't need to check for overflows if ret is of primitive type
629 // because the depth does not change.
635 preventOverflow(v
, [&v
, this]() {
636 if (v
->isCollection()) {
637 serializeCollection(v
.get());
638 } else if (v
->instanceof(c_Closure::classof())) {
639 // We serialize closures as "{}" in JSON mode to be compatible
640 // with PHP. And issue a warning in HipHop syntax.
641 if (RuntimeOption::EnableHipHopSyntax
) {
642 m_buf
->append("null");
643 json_set_last_error_code(
644 json_error_codes::JSON_ERROR_UNSUPPORTED_TYPE
);
649 auto props
= v
->toArray(true);
650 pushObjectInfo(v
->getClassName(), v
->getId(), 'O');
651 serializeArray(props
);
660 void VariableSerializer::preventOverflow(const Object
& v
,
661 const std::function
<void()>& func
) {
662 TypedValue tv
= make_tv
<KindOfObject
>(const_cast<ObjectData
*>(v
.get()));
663 if (incNestedLevel(tv
)) {
671 void VariableSerializer::write(const Variant
& v
, bool isArrayKey
/*= false */) {
672 setReferenced(v
.isReferenced());
673 if (m_type
== Type::DebugDump
) {
674 setRefCount(v
.getRefCount());
676 if (!isArrayKey
&& v
.isObject()) {
680 serializeVariant(v
, isArrayKey
);
683 void VariableSerializer::writeNull() {
688 case Type::VarExport
:
689 case Type::PHPOutput
:
690 m_buf
->append("NULL");
693 case Type::DebugDump
:
695 m_buf
->append("NULL");
699 case Type::Serialize
:
701 case Type::APCSerialize
:
702 case Type::DebuggerSerialize
:
706 case Type::DebuggerDump
:
707 m_buf
->append("null");
715 void VariableSerializer::writeOverflow(const TypedValue
& tv
) {
716 bool wasRef
= m_referenced
;
717 setReferenced(false);
720 if (!m_objClass
.empty()) {
721 m_buf
->append(m_objClass
);
722 m_buf
->append(" Object\n");
724 m_buf
->append("Array\n");
726 m_buf
->append(" *RECURSION*");
728 case Type::VarExport
:
729 case Type::PHPOutput
:
730 throwNestingException();
732 case Type::DebugDump
:
733 case Type::DebuggerDump
:
735 m_buf
->append("*RECURSION*\n");
737 case Type::DebuggerSerialize
:
738 if (m_maxLevelDebugger
> 0 && m_levelDebugger
> m_maxLevelDebugger
) {
739 // Not recursion, just cut short of print
740 m_buf
->append("s:12:\"...(omitted)\";", 20);
744 case Type::Serialize
:
746 case Type::APCSerialize
:
748 int optId
= m_refs
[tv
].m_id
;
749 assert(optId
!= NO_ID
);
750 bool isObject
= tv
.m_type
== KindOfResource
|| tv
.m_type
== KindOfObject
;
753 m_buf
->append(optId
);
755 } else if (isObject
) {
757 m_buf
->append(optId
);
765 json_set_last_error_code(json_error_codes::JSON_ERROR_RECURSION
);
766 m_buf
->append("null");
774 void VariableSerializer::writeRefCount() {
775 if (m_type
== Type::DebugDump
) {
776 m_buf
->append(" refcount(");
777 m_buf
->append(m_refCount
);
783 void VariableSerializer::writeArrayHeader(int size
, bool isVectorData
,
784 VariableSerializer::ArrayKind kind
) {
785 m_arrayInfos
.push_back(ArrayInfo());
786 ArrayInfo
&info
= m_arrayInfos
.back();
787 info
.first_element
= true;
788 info
.indent_delta
= 0;
792 case Type::DebuggerDump
:
794 if (!m_rsrcName
.empty()) {
795 m_buf
->append("Resource id #");
796 m_buf
->append(m_rsrcId
);
797 if (m_type
== Type::DebuggerDump
) {
798 m_buf
->append(" of type ");
799 m_buf
->append(m_rsrcName
);
802 } else if (!m_objClass
.empty()) {
803 m_buf
->append(m_objClass
);
804 m_buf
->append(" Object\n");
807 case ArrayKind::Dict
:
808 m_buf
->append("Dict\n");
811 m_buf
->append("Vec\n");
813 case ArrayKind::Keyset
:
814 m_buf
->append("Keyset\n");
817 case ArrayKind::VArray
:
818 case ArrayKind::DArray
:
819 m_buf
->append("Array\n");
827 m_buf
->append("(\n");
828 m_indent
+= (info
.indent_delta
= 4);
830 case Type::VarExport
:
831 case Type::PHPOutput
:
832 if (m_indent
> 0 && m_rsrcName
.empty()) {
836 if (!m_objClass
.empty()) {
837 m_buf
->append(m_objClass
);
838 if (m_objCode
== 'O') {
839 m_buf
->append("::__set_state(array(\n");
841 assert(m_objCode
== 'V' || m_objCode
== 'K');
842 m_buf
->append(" {\n");
844 } else if (!m_rsrcName
.empty()) {
845 m_buf
->append("NULL");
848 case ArrayKind::Dict
:
849 m_buf
->append("dict [\n");
852 m_buf
->append("vec [\n");
854 case ArrayKind::Keyset
:
855 m_buf
->append("keyset [\n");
858 case ArrayKind::VArray
:
859 case ArrayKind::DArray
:
860 m_buf
->append("array (\n");
864 m_indent
+= (info
.indent_delta
= 2);
867 case Type::DebugDump
:
869 if (!m_rsrcName
.empty()) {
870 m_buf
->append("resource(");
871 m_buf
->append(m_rsrcId
);
872 m_buf
->append(") of type (");
873 m_buf
->append(m_rsrcName
);
874 m_buf
->append(")\n");
876 } else if (!m_objClass
.empty()) {
877 m_buf
->append("object(");
878 m_buf
->append(m_objClass
);
880 m_buf
->append(m_objId
);
884 case ArrayKind::Dict
:
885 m_buf
->append("dict");
888 m_buf
->append("vec");
890 case ArrayKind::Keyset
:
891 m_buf
->append("keyset");
894 case ArrayKind::VArray
:
895 case ArrayKind::DArray
:
896 m_buf
->append("array");
904 // ...so to strictly follow PHP's output
905 if (m_type
== Type::VarDump
) {
911 m_buf
->append("{\n");
912 m_indent
+= (info
.indent_delta
= 2);
914 case Type::Serialize
:
916 case Type::APCSerialize
:
917 case Type::DebuggerSerialize
:
918 if (!m_rsrcName
.empty() && m_type
== Type::DebuggerSerialize
) {
920 m_buf
->append(m_rsrcId
);
922 m_buf
->append((int)m_rsrcName
.size());
923 m_buf
->append(":\"");
924 m_buf
->append(m_rsrcName
);
925 m_buf
->append("\"{");
926 } else if (!m_objClass
.empty()) {
927 m_buf
->append(m_objCode
);
929 m_buf
->append((int)m_objClass
.size());
930 m_buf
->append(":\"");
931 m_buf
->append(m_objClass
);
932 m_buf
->append("\":");
937 case ArrayKind::Dict
:
943 case ArrayKind::Keyset
:
949 case ArrayKind::VArray
:
952 case ArrayKind::DArray
:
962 (m_objClass
.empty() || m_objCode
== 'V' || m_objCode
== 'K') &&
964 kind
!= ArrayKind::Dict
;
966 if (info
.is_vector
&& m_type
== Type::JSON
) {
967 info
.is_vector
= (m_option
& k_JSON_FORCE_OBJECT
)
968 ? false : info
.is_vector
;
971 if (info
.is_vector
|| kind
== ArrayKind::Keyset
) {
977 if (m_type
== Type::JSON
&& (m_option
& k_JSON_PRETTY_PRINT
) &&
980 m_indent
+= (info
.indent_delta
= 4);
989 // ...so we don't mess up next array output
990 if (!m_objClass
.empty() || !m_rsrcName
.empty()) {
992 info
.is_object
= true;
994 info
.is_object
= false;
998 void VariableSerializer::writePropertyKey(const String
& prop
) {
999 const char *key
= prop
.data();
1000 int kl
= prop
.size();
1002 const char *cls
= key
+ 1;
1004 assert(key
[2] == 0);
1005 m_buf
->append(key
+ 3, kl
- 3);
1006 const char prot
[] = "\":protected";
1007 int o
= m_type
== Type::PrintR
? 1 : 0;
1008 m_buf
->append(prot
+ o
, sizeof(prot
) - 1 - o
);
1010 int l
= strlen(cls
);
1011 m_buf
->append(cls
+ l
+ 1, kl
- l
- 2);
1012 int o
= m_type
== Type::PrintR
? 1 : 0;
1013 m_buf
->append(&"\":\""[o
], 3 - 2*o
);
1014 m_buf
->append(cls
, l
);
1015 const char priv
[] = "\":private";
1016 m_buf
->append(priv
+ o
, sizeof(priv
) - 1 - o
);
1019 m_buf
->append(prop
);
1020 if (m_type
!= Type::PrintR
&& m_type
!= Type::DebuggerDump
) {
1026 /* key MUST be a non-reference string or int */
1027 void VariableSerializer::writeArrayKey(
1029 VariableSerializer::ArrayKind kind
1031 using AK
= VariableSerializer::ArrayKind
;
1032 auto const keyCell
= tvAssertCell(key
.asTypedValue());
1033 bool const skey
= isStringType(keyCell
->m_type
);
1035 ArrayInfo
&info
= m_arrayInfos
.back();
1038 case Type::DebuggerDump
:
1039 case Type::PrintR
: {
1041 if (kind
== AK::Keyset
) return;
1043 if (info
.is_object
&& skey
) {
1044 writePropertyKey(String
{keyCell
->m_data
.pstr
});
1048 m_buf
->append("] => ");
1052 case Type::VarExport
:
1053 case Type::PHPOutput
:
1055 if (kind
== AK::Vec
|| kind
== AK::Keyset
) return;
1057 m_buf
->append(" => ");
1061 case Type::DebugDump
:
1062 if (kind
== AK::Vec
|| kind
== AK::Keyset
) return;
1066 m_buf
->append(keyCell
->m_data
.num
);
1069 if (info
.is_object
) {
1070 writePropertyKey(String
{keyCell
->m_data
.pstr
});
1072 m_buf
->append(keyCell
->m_data
.pstr
);
1076 m_buf
->append("]=>\n");
1079 case Type::APCSerialize
:
1080 if (kind
== AK::Vec
|| kind
== AK::Keyset
|| kind
== AK::VArray
) return;
1082 write(StrNR(keyCell
->m_data
.pstr
).asString());
1086 case Type::Serialize
:
1087 case Type::Internal
:
1088 case Type::DebuggerSerialize
:
1089 if (kind
== AK::Vec
|| kind
== AK::Keyset
|| kind
== AK::VArray
) return;
1094 if (!info
.is_vector
&& kind
!= ArrayKind::Keyset
) {
1095 if (!info
.first_element
) {
1098 if (UNLIKELY(m_option
& k_JSON_PRETTY_PRINT
)) {
1099 if (!info
.first_element
) {
1100 m_buf
->append("\n");
1105 auto const sdata
= keyCell
->m_data
.pstr
;
1106 const char *k
= sdata
->data();
1107 int len
= sdata
->size();
1108 if (info
.is_object
&& !*k
&& len
) {
1113 write(k
, len
, true);
1116 m_buf
->append(keyCell
->m_data
.num
);
1120 if (UNLIKELY(m_option
& k_JSON_PRETTY_PRINT
)) {
1132 void VariableSerializer::writeCollectionKey(
1134 VariableSerializer::ArrayKind kind
1136 if (m_type
== Type::Serialize
||
1137 m_type
== Type::Internal
||
1138 m_type
== Type::APCSerialize
||
1139 m_type
== Type::DebuggerSerialize
) {
1142 writeArrayKey(key
, kind
);
1145 void VariableSerializer::writeArrayValue(
1146 const Variant
& value
,
1147 VariableSerializer::ArrayKind kind
1150 case Type::Serialize
:
1151 case Type::Internal
:
1152 case Type::APCSerialize
:
1153 case Type::DebuggerSerialize
:
1154 // Do not count referenced values after the first
1155 if (!(value
.isReferenced() &&
1156 m_refs
[*value
.asTypedValue()].m_id
!= NO_ID
)) {
1162 case Type::DebuggerDump
:
1165 m_buf
->append('\n');
1168 case Type::VarExport
:
1169 case Type::PHPOutput
:
1171 m_buf
->append(",\n");
1175 ArrayInfo
&info
= m_arrayInfos
.back();
1176 if (info
.is_vector
|| kind
== ArrayKind::Keyset
) {
1177 if (!info
.first_element
) {
1180 if (UNLIKELY(m_option
& k_JSON_PRETTY_PRINT
)) {
1181 if (!info
.first_element
) {
1182 m_buf
->append("\n");
1196 ArrayInfo
&last_info
= m_arrayInfos
.back();
1197 last_info
.first_element
= false;
1200 void VariableSerializer::writeArrayFooter(
1201 VariableSerializer::ArrayKind kind
1203 ArrayInfo
&info
= m_arrayInfos
.back();
1205 m_indent
-= info
.indent_delta
;
1207 case Type::DebuggerDump
:
1209 if (m_rsrcName
.empty()) {
1211 m_buf
->append(")\n");
1217 case Type::VarExport
:
1218 case Type::PHPOutput
:
1219 if (m_rsrcName
.empty()) {
1222 if (info
.is_object
&& m_objCode
) {
1223 if (m_objCode
== 'O') {
1224 m_buf
->append("))");
1226 assert(m_objCode
== 'V' || m_objCode
== 'K');
1229 } else if (m_rsrcName
.empty()) { // for rsrc, only write NULL in arrayHeader
1231 case ArrayKind::Dict
:
1232 case ArrayKind::Vec
:
1233 case ArrayKind::Keyset
:
1236 case ArrayKind::PHP
:
1237 case ArrayKind::VArray
:
1238 case ArrayKind::DArray
:
1245 case Type::DebugDump
:
1246 if (m_rsrcName
.empty()) {
1248 m_buf
->append("}\n");
1251 case Type::Serialize
:
1252 case Type::Internal
:
1253 case Type::APCSerialize
:
1254 case Type::DebuggerSerialize
:
1258 if (m_type
== Type::JSON
&& (m_option
& k_JSON_PRETTY_PRINT
) &&
1260 m_buf
->append("\n");
1263 if (info
.is_vector
|| kind
== ArrayKind::Keyset
) {
1274 m_arrayInfos
.pop_back();
1277 void VariableSerializer::writeSerializableObject(const String
& clsname
,
1278 const String
& serialized
) {
1279 m_buf
->append("C:");
1280 m_buf
->append(clsname
.size());
1281 m_buf
->append(":\"");
1282 m_buf
->append(clsname
.data(), clsname
.size());
1283 m_buf
->append("\":");
1284 m_buf
->append(serialized
.size());
1285 m_buf
->append(":{");
1286 m_buf
->append(serialized
.data(), serialized
.size());
1290 ///////////////////////////////////////////////////////////////////////////////
1292 void VariableSerializer::indent() {
1293 for (int i
= 0; i
< m_indent
; i
++) {
1297 if (m_indent
> 0 && (m_type
== Type::VarDump
||
1298 m_type
== Type::DebugDump
)) {
1301 m_referenced
= false;
1305 bool VariableSerializer::incNestedLevel(const TypedValue
& tv
) {
1309 case Type::VarExport
:
1310 case Type::PHPOutput
:
1313 case Type::DebugDump
:
1314 case Type::DebuggerDump
:
1315 return ++m_refs
[tv
].m_count
>= m_maxCount
;
1317 if (m_currentDepth
> m_maxDepth
) {
1318 json_set_last_error_code(json_error_codes::JSON_ERROR_DEPTH
);
1320 return ++m_refs
[tv
].m_count
>= m_maxCount
;
1321 case Type::DebuggerSerialize
:
1322 if (m_maxLevelDebugger
> 0 && ++m_levelDebugger
> m_maxLevelDebugger
) {
1326 case Type::Serialize
:
1327 case Type::Internal
:
1328 case Type::APCSerialize
:
1330 auto& ref
= m_refs
[tv
];
1331 int ct
= ++ref
.m_count
;
1332 bool isObject
= tv
.m_type
== KindOfResource
|| tv
.m_type
== KindOfObject
;
1333 if (ref
.m_id
!= NO_ID
&& (m_referenced
|| isObject
)) {
1336 ref
.m_id
= m_valueCount
;
1337 return ct
>= (m_maxCount
- 1);
1347 void VariableSerializer::decNestedLevel(const TypedValue
& tv
) {
1349 --m_refs
[tv
].m_count
;
1350 if (m_type
== Type::DebuggerSerialize
&& m_maxLevelDebugger
> 0) {
1355 void VariableSerializer::serializeRef(const TypedValue
* tv
, bool isArrayKey
) {
1356 assert(tv
->m_type
== KindOfRef
);
1357 // Ugly, but behavior is different for serialize
1358 if (getType() == VariableSerializer::Type::Serialize
||
1359 getType() == VariableSerializer::Type::Internal
||
1360 getType() == VariableSerializer::Type::APCSerialize
||
1361 getType() == VariableSerializer::Type::DebuggerSerialize
) {
1362 if (incNestedLevel(*tv
)) {
1365 // Tell the inner variant to skip the nesting check for data inside
1366 serializeVariant(*tv
->m_data
.pref
->var(), isArrayKey
, true);
1368 decNestedLevel(*tv
);
1370 serializeVariant(*tv
->m_data
.pref
->var(), isArrayKey
);
1375 void VariableSerializer::serializeVariant(const Variant
& self
,
1376 bool isArrayKey
/* = false */,
1377 bool skipNestCheck
/* = false */,
1378 bool noQuotes
/* = false */) {
1379 auto tv
= self
.asTypedValue();
1381 switch (tv
->m_type
) {
1384 assert(!isArrayKey
);
1389 assert(!isArrayKey
);
1390 write(tv
->m_data
.num
!= 0);
1394 write(tv
->m_data
.num
);
1398 write(tv
->m_data
.dbl
);
1401 case KindOfPersistentString
:
1403 write(tv
->m_data
.pstr
->data(),
1404 tv
->m_data
.pstr
->size(), isArrayKey
, noQuotes
);
1407 case KindOfPersistentVec
:
1409 assert(!isArrayKey
);
1410 assert(tv
->m_data
.parr
->isVecArray());
1411 serializeArray(tv
->m_data
.parr
, skipNestCheck
);
1414 case KindOfPersistentDict
:
1416 assert(!isArrayKey
);
1417 assert(tv
->m_data
.parr
->isDict());
1418 serializeArray(tv
->m_data
.parr
, skipNestCheck
);
1421 case KindOfPersistentKeyset
:
1423 assert(!isArrayKey
);
1424 assert(tv
->m_data
.parr
->isKeyset());
1425 serializeArray(tv
->m_data
.parr
, skipNestCheck
);
1428 case KindOfPersistentArray
:
1430 assert(!isArrayKey
);
1431 assert(tv
->m_data
.parr
->isPHPArray());
1432 serializeArray(tv
->m_data
.parr
, skipNestCheck
);
1436 assert(!isArrayKey
);
1437 serializeObject(tv
->m_data
.pobj
);
1440 case KindOfResource
:
1441 assert(!isArrayKey
);
1442 serializeResource(tv
->m_data
.pres
->data());
1446 serializeRef(tv
, isArrayKey
);
1452 void VariableSerializer::serializeResourceImpl(const ResourceData
* res
) {
1453 pushResourceInfo(res
->o_getResourceName(), res
->getId());
1454 serializeArray(empty_array());
1458 void VariableSerializer::serializeResource(const ResourceData
* res
) {
1459 TypedValue tv
= make_tv
<KindOfResource
>(const_cast<ResourceHdr
*>(res
->hdr()));
1460 if (UNLIKELY(incNestedLevel(tv
))) {
1462 } else if (auto trace
= dynamic_cast<const CompactTrace
*>(res
)) {
1463 serializeArray(trace
->extract());
1465 serializeResourceImpl(res
);
1470 void VariableSerializer::serializeString(const String
& str
) {
1472 write(str
.data(), str
.size());
1478 void VariableSerializer::serializeArrayImpl(const ArrayData
* arr
) {
1479 using AK
= VariableSerializer::ArrayKind
;
1480 AK kind
= getKind(arr
);
1483 arr
->isVectorData(),
1489 [&](Cell k
, TypedValue v
) {
1490 writeArrayKey(VarNR(k
), kind
);
1491 writeArrayValue(VarNR(v
), kind
);
1495 writeArrayFooter(kind
);
1498 void VariableSerializer::serializeArray(const ArrayData
* arr
,
1499 bool skipNestCheck
/* = false */) {
1500 if (arr
->size() == 0) {
1501 writeArrayHeader(0, arr
->isVectorData(), getKind(arr
));
1502 writeArrayFooter(getKind(arr
));
1505 if (!skipNestCheck
) {
1506 TypedValue tv
= make_array_like_tv(const_cast<ArrayData
*>(arr
));
1507 if (incNestedLevel(tv
)) {
1510 serializeArrayImpl(arr
);
1514 // If isObject, the array is temporary and we should not check or save
1516 serializeArrayImpl(arr
);
1520 void VariableSerializer::serializeArray(const Array
& arr
,
1521 bool isObject
/* = false */) {
1522 if (!arr
.isNull()) {
1523 serializeArray(arr
.get(), isObject
);
1529 void VariableSerializer::serializeCollection(ObjectData
* obj
) {
1530 using AK
= VariableSerializer::ArrayKind
;
1531 int64_t sz
= collections::getSize(obj
);
1532 auto type
= obj
->collectionType();
1534 if (isMapCollection(type
)) {
1535 pushObjectInfo(obj
->getClassName(), obj
->getId(), 'K');
1536 writeArrayHeader(sz
, false, AK::PHP
);
1537 for (ArrayIter
iter(obj
); iter
; ++iter
) {
1538 writeCollectionKey(iter
.first(), AK::PHP
);
1539 writeArrayValue(iter
.second(), AK::PHP
);
1541 writeArrayFooter(AK::PHP
);
1544 assertx(isVectorCollection(type
) ||
1545 isSetCollection(type
) ||
1546 (type
== CollectionType::Pair
));
1547 pushObjectInfo(obj
->getClassName(), obj
->getId(), 'V');
1548 writeArrayHeader(sz
, true, AK::PHP
);
1549 auto ser_type
= getType();
1550 if (ser_type
== VariableSerializer::Type::Serialize
||
1551 ser_type
== VariableSerializer::Type::Internal
||
1552 ser_type
== VariableSerializer::Type::APCSerialize
||
1553 ser_type
== VariableSerializer::Type::DebuggerSerialize
||
1554 ser_type
== VariableSerializer::Type::VarExport
||
1555 ser_type
== VariableSerializer::Type::PHPOutput
) {
1556 // For the 'V' serialization format, we don't print out keys
1557 // for Serialize, APCSerialize, DebuggerSerialize
1558 bool const should_indent
=
1559 ser_type
== VariableSerializer::Type::VarExport
||
1560 ser_type
== VariableSerializer::Type::PHPOutput
;
1561 for (ArrayIter
iter(obj
); iter
; ++iter
) {
1562 if (should_indent
) {
1565 writeArrayValue(iter
.second(), AK::PHP
);
1568 if (isSetCollection(type
)) {
1569 bool const should_indent
=
1570 ser_type
== VariableSerializer::Type::PrintR
||
1571 ser_type
== VariableSerializer::Type::DebuggerDump
;
1572 for (ArrayIter
iter(obj
); iter
; ++iter
) {
1573 if (should_indent
) {
1576 writeArrayValue(iter
.second(), AK::PHP
);
1579 for (ArrayIter
iter(obj
); iter
; ++iter
) {
1580 writeCollectionKey(iter
.first(), AK::PHP
);
1581 writeArrayValue(iter
.second(), AK::PHP
);
1585 writeArrayFooter(AK::PHP
);
1590 /* Get properties from the actual object unless we're
1591 * serializing for var_dump()/print_r() and the object
1592 * exports a __debugInfo() magic method.
1593 * In which case, call that and use the array it returns.
1595 Array
VariableSerializer::getSerializeProps(const ObjectData
* obj
) const {
1596 if (getType() == VariableSerializer::Type::VarExport
) {
1597 Array props
= Array::Create();
1598 for (ArrayIter
iter(obj
->toArray()); iter
; ++iter
) {
1599 auto key
= iter
.first().toString();
1600 // Jump over any class attribute mangling
1601 if (key
[0] == '\0' && key
.size() > 0) {
1605 } while (key
[sizeToCut
] != '\0');
1606 key
= key
.substr(sizeToCut
+1);
1608 props
.setWithRef(key
, iter
.secondVal());
1612 if ((getType() != VariableSerializer::Type::PrintR
) &&
1613 (getType() != VariableSerializer::Type::VarDump
)) {
1614 return obj
->toArray();
1616 auto cls
= obj
->getVMClass();
1617 auto debuginfo
= cls
->lookupMethod(s_debugInfo
.get());
1619 // When ArrayIterator is cast to an array, it returns its array object,
1620 // however when it's being var_dump'd or print_r'd, it shows its properties
1621 if (UNLIKELY(obj
->instanceof(SystemLib::s_ArrayIteratorClass
))) {
1622 auto ret
= Array::Create();
1623 obj
->o_getArray(ret
);
1627 // Same with Closure, since it's a dynamic object but still has its own
1628 // different behavior for var_dump and cast to array
1629 if (UNLIKELY(obj
->instanceof(c_Closure::classof()))) {
1630 auto ret
= Array::Create();
1631 obj
->o_getArray(ret
);
1635 return obj
->toArray();
1637 if (debuginfo
->attrs() & (AttrPrivate
|AttrProtected
|
1638 AttrAbstract
|AttrStatic
)) {
1639 raise_warning("%s::__debugInfo() must be public and non-static",
1640 cls
->name()->data());
1641 return obj
->toArray();
1643 Variant ret
= const_cast<ObjectData
*>(obj
)->o_invoke_few_args(s_debugInfo
, 0);
1644 if (ret
.isArray()) {
1645 return ret
.toArray();
1648 return empty_array();
1650 raise_error("__debugInfo() must return an array");
1654 void VariableSerializer::serializeObjectImpl(const ObjectData
* obj
) {
1655 bool handleSleep
= false;
1656 Variant serializableNativeData
= init_null();
1658 auto const type
= getType();
1660 if (obj
->isCollection()) {
1661 serializeCollection(const_cast<ObjectData
*>(obj
));
1665 if (LIKELY(type
== VariableSerializer::Type::Serialize
||
1666 type
== VariableSerializer::Type::Internal
||
1667 type
== VariableSerializer::Type::APCSerialize
)) {
1668 if (obj
->instanceof(SystemLib::s_SerializableClass
)) {
1669 assert(!obj
->isCollection());
1671 const_cast<ObjectData
*>(obj
)->o_invoke_few_args(s_serialize
, 0);
1672 if (ret
.isString()) {
1673 writeSerializableObject(obj
->getClassName(), ret
.toString());
1674 } else if (ret
.isNull()) {
1677 raise_error("%s::serialize() must return a string or NULL",
1678 obj
->getClassName().data());
1682 // Only serialize CPP extension type instances which can actually
1683 // be deserialized. Otherwise, raise a warning and serialize
1685 // Similarly, do not try to serialize WaitHandles
1686 // as they contain internal state via non-NativeData means.
1687 auto cls
= obj
->getVMClass();
1688 if ((cls
->instanceCtor() && !cls
->isCppSerializable()) ||
1689 obj
->getAttribute(ObjectData::IsWaitHandle
)) {
1690 raise_warning("Attempted to serialize unserializable builtin class %s",
1691 obj
->getVMClass()->preClass()->name()->data());
1692 Variant placeholder
= init_null();
1693 serializeVariant(placeholder
);
1696 if (obj
->getAttribute(ObjectData::HasSleep
)) {
1698 ret
= const_cast<ObjectData
*>(obj
)->invokeSleep();
1700 if (obj
->getAttribute(ObjectData::HasNativeData
)) {
1701 auto* ndi
= cls
->getNativeDataInfo();
1702 if (ndi
->isSerializable()) {
1703 serializableNativeData
= Native::nativeDataSleep(obj
);
1706 } else if (UNLIKELY(type
== VariableSerializer::Type::DebuggerSerialize
)) {
1707 // Don't try to serialize a CPP extension class which doesn't
1708 // support serialization. Just send the class name instead.
1709 if (obj
->isCppBuiltin() && !obj
->getVMClass()->isCppSerializable()) {
1710 write(obj
->getClassName());
1715 if (UNLIKELY(handleSleep
)) {
1716 assert(!obj
->isCollection());
1717 if (ret
.isArray()) {
1718 Array wanted
= Array::Create();
1719 assert(isArrayType(ret
.getRawType())); // can't be KindOfRef
1720 const Array
&props
= ret
.asCArrRef();
1721 for (ArrayIter
iter(props
); iter
; ++iter
) {
1722 String memberName
= iter
.second().toString();
1723 String propName
= memberName
;
1724 auto obj_cls
= obj
->getVMClass();
1725 Class
* ctx
= obj_cls
;
1726 auto attrMask
= AttrNone
;
1727 if (memberName
.data()[0] == 0) {
1728 int subLen
= memberName
.find('\0', 1) + 1;
1730 if (subLen
== 3 && memberName
.data()[1] == '*') {
1731 attrMask
= AttrProtected
;
1732 memberName
= memberName
.substr(subLen
);
1734 attrMask
= AttrPrivate
;
1735 String cls
= memberName
.substr(1, subLen
- 2);
1736 ctx
= Unit::lookupClass(cls
.get());
1738 memberName
= memberName
.substr(subLen
);
1746 auto const lookup
= obj_cls
->getDeclPropIndex(ctx
, memberName
.get());
1747 auto const slot
= lookup
.prop
;
1749 if (slot
!= kInvalidSlot
&& lookup
.accessible
) {
1750 auto const prop
= obj
->propRvalAtOffset(slot
);
1751 if (prop
.type() != KindOfUninit
) {
1752 auto const attrs
= obj_cls
->declProperties()[slot
].attrs
;
1753 if (attrs
& AttrPrivate
) {
1754 memberName
= concat4(s_zero
, ctx
->nameStr(),
1755 s_zero
, memberName
);
1756 } else if (attrs
& AttrProtected
) {
1757 memberName
= concat(s_protected_prefix
, memberName
);
1759 if (!attrMask
|| (attrMask
& attrs
) == attrMask
) {
1760 wanted
.set(memberName
, prop
.tv());
1766 UNLIKELY(obj
->getAttribute(ObjectData::HasDynPropArr
))) {
1767 if (auto const prop
= obj
->dynPropArray()->rval(propName
.get())) {
1768 wanted
.set(propName
, prop
.tv());
1772 raise_notice("serialize(): \"%s\" returned as member variable from "
1773 "__sleep() but does not exist", propName
.data());
1774 wanted
.set(propName
, init_null());
1776 pushObjectInfo(obj
->getClassName(), obj
->getId(), 'O');
1777 if (!serializableNativeData
.isNull()) {
1778 wanted
.set(s_serializedNativeDataKey
, serializableNativeData
);
1780 serializeArray(wanted
, true);
1783 raise_notice("serialize(): __sleep should return an array only "
1784 "containing the names of instance-variables to "
1786 serializeVariant(uninit_null());
1789 if (type
== VariableSerializer::Type::VarExport
&&
1790 obj
->instanceof(c_Closure::classof())) {
1791 write(obj
->getClassName());
1793 auto className
= obj
->getClassName();
1794 Array properties
= getSerializeProps(obj
);
1795 if (type
== VariableSerializer::Type::DebuggerSerialize
) {
1797 auto val
= const_cast<ObjectData
*>(obj
)->invokeToDebugDisplay();
1798 if (val
.isInitialized()) {
1799 auto const lval
= properties
.lvalAt(s_PHP_DebugDisplay
);
1800 tvSet(*val
.asTypedValue(), lval
);
1803 raise_warning("%s::__toDebugDisplay() throws exception",
1804 obj
->getClassName().data());
1807 if (type
== VariableSerializer::Type::DebuggerDump
) {
1808 // Expect to display as their stringified classname.
1809 if (obj
->instanceof(c_Closure::classof())) {
1810 write(obj
->getVMClass()->nameStr());
1814 // If we have a DebugDisplay prop saved, use it.
1815 auto const debugDisp
= obj
->getProp(nullptr, s_PHP_DebugDisplay
.get());
1816 if (debugDisp
.has_val()) {
1817 serializeVariant(tvAsCVarRef(debugDisp
.tv_ptr()), false, false, true);
1820 // Otherwise compute it if we have a __toDebugDisplay method.
1821 auto val
= const_cast<ObjectData
*>(obj
)->invokeToDebugDisplay();
1822 if (val
.isInitialized()) {
1823 serializeVariant(val
, false, false, true);
1827 if (className
.get() == s_PHP_Incomplete_Class
.get() &&
1828 (type
== VariableSerializer::Type::Serialize
||
1829 type
== VariableSerializer::Type::Internal
||
1830 type
== VariableSerializer::Type::APCSerialize
||
1831 type
== VariableSerializer::Type::DebuggerSerialize
||
1832 type
== VariableSerializer::Type::DebuggerDump
)) {
1833 auto const cname
= obj
->getProp(
1835 s_PHP_Incomplete_Class_Name
.get()
1837 if (cname
.has_val() && isStringType(cname
.type())) {
1838 pushObjectInfo(StrNR(cname
.val().pstr
), obj
->getId(), 'O');
1839 properties
.remove(s_PHP_Incomplete_Class_Name
, true);
1840 serializeArray(properties
, true);
1845 pushObjectInfo(className
, obj
->getId(), 'O');
1846 if (!serializableNativeData
.isNull()) {
1847 properties
.set(s_serializedNativeDataKey
, serializableNativeData
);
1849 serializeArray(properties
, true);
1855 void VariableSerializer::serializeObject(const ObjectData
* obj
) {
1856 TypedValue tv
= make_tv
<KindOfObject
>(const_cast<ObjectData
*>(obj
));
1857 if (UNLIKELY(incNestedLevel(tv
))) {
1860 serializeObjectImpl(obj
);
1865 void VariableSerializer::serializeObject(const Object
& obj
) {
1867 serializeObject(obj
.get());