Don't return Variant& from Array functions
[hiphop-php.git] / hphp / runtime / base / variable-serializer.cpp
blob9bd6b8bac8131c4766a953fdccf3b140ef76e7fc
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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"
27 #include <cmath>
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"
41 namespace HPHP {
42 ///////////////////////////////////////////////////////////////////////////////
44 extern const StaticString
45 s_serializedNativeDataKey(std::string("\0native", 7));
47 const StaticString
48 s_JsonSerializable("JsonSerializable"),
49 s_jsonSerialize("jsonSerialize"),
50 s_serialize("serialize"),
51 s_zero("\0", 1),
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 */)
76 : m_type(type)
77 , m_option(option)
78 , m_buf(nullptr)
79 , m_indent(0)
80 , m_valueCount(0)
81 , m_referenced(false)
82 , m_keepDVArrays{type != Type::Serialize}
83 , m_refCount(1)
84 , m_objId(0)
85 , m_objCode(0)
86 , m_rsrcId(0)
87 , m_maxCount(maxRecur)
88 , m_levelDebugger(0)
89 , m_currentDepth(0)
90 , m_maxDepth(0)
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,
111 char objCode) {
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;
117 m_objId = objId;
118 m_objCode = objCode;
119 m_rsrcName.reset();
120 m_rsrcId = 0;
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 }
127 m_objClass.reset();
128 m_objId = 0;
129 m_objCode = 0;
130 m_rsrcName = rsrcName;
131 m_rsrcId = rsrcId;
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 =
145 StringData::MaxSize;
147 void VariableSerializer::popResourceInfo() {
148 popObjectInfo();
151 String VariableSerializer::serialize(const Variant& v, bool ret,
152 bool keepCount /* = false */) {
153 StringBuffer buf;
154 m_buf = &buf;
155 if (ret) {
156 buf.setOutputLimit(serializationSizeLimit);
157 } else {
158 buf.setOutputLimit(StringData::MaxSize);
160 m_valueCount = keepCount ? m_valueCount + 1 : 1;
161 write(v);
162 if (ret) {
163 return m_buf->detach();
164 } else {
165 String str = m_buf->detach();
166 g_context->write(str);
168 return String();
171 String VariableSerializer::serializeValue(const Variant& v, bool limit) {
172 StringBuffer buf;
173 m_buf = &buf;
174 if (limit) {
175 buf.setOutputLimit(serializationSizeLimit);
177 m_valueCount = 1;
178 write(v);
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) {
186 assert(false);
187 return String();
189 StringBuffer buf;
190 m_buf = &buf;
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
197 try {
198 write(v);
199 } catch (StringBufferLimitException &e) {
200 return e.m_result;
202 return m_buf->detach();
205 ///////////////////////////////////////////////////////////////////////////////
207 void VariableSerializer::write(bool v) {
208 switch (m_type) {
209 case Type::PrintR:
210 if (v) m_buf->append(1);
211 break;
212 case Type::VarExport:
213 case Type::PHPOutput:
214 case Type::JSON:
215 case Type::DebuggerDump:
216 m_buf->append(v ? "true" : "false");
217 break;
218 case Type::VarDump:
219 case Type::DebugDump:
220 indent();
221 m_buf->append(v ? "bool(true)" : "bool(false)");
222 writeRefCount();
223 m_buf->append('\n');
224 break;
225 case Type::Serialize:
226 case Type::Internal:
227 case Type::APCSerialize:
228 case Type::DebuggerSerialize:
229 m_buf->append(v ? "b:1;" : "b:0;");
230 break;
231 default:
232 assert(false);
233 break;
237 void VariableSerializer::write(int64_t v) {
238 switch (m_type) {
239 case Type::PrintR:
240 case Type::VarExport:
241 case Type::PHPOutput:
242 case Type::JSON:
243 case Type::DebuggerDump:
244 m_buf->append(v);
245 break;
246 case Type::VarDump:
247 indent();
248 m_buf->append("int(");
249 m_buf->append(v);
250 m_buf->append(")\n");
251 break;
252 case Type::DebugDump:
253 indent();
254 m_buf->append("long(");
255 m_buf->append(v);
256 m_buf->append(')');
257 writeRefCount();
258 m_buf->append('\n');
259 break;
260 case Type::Serialize:
261 case Type::Internal:
262 case Type::APCSerialize:
263 case Type::DebuggerSerialize:
264 m_buf->append("i:");
265 m_buf->append(v);
266 m_buf->append(';');
267 break;
268 default:
269 assert(false);
270 break;
274 void VariableSerializer::write(double v) {
275 auto const precision = 14;
276 auto const serde_precision = 17;
278 switch (m_type) {
279 case Type::JSON:
280 if (!std::isinf(v) && !std::isnan(v)) {
281 char *buf;
282 vspprintf(&buf, 0, "%.*k", precision, v);
283 m_buf->append(buf);
284 if (m_option & k_JSON_PRESERVE_ZERO_FRACTION
285 && strchr(buf, '.') == nullptr) {
286 m_buf->append(".0");
288 free(buf);
289 } else {
290 json_set_last_error_code(json_error_codes::JSON_ERROR_INF_OR_NAN);
292 m_buf->append('0');
294 break;
295 case Type::VarExport:
296 case Type::PHPOutput:
297 case Type::PrintR:
298 case Type::DebuggerDump:
300 char *buf;
301 bool isExport = m_type == Type::VarExport || m_type == Type::PHPOutput;
302 vspprintf(&buf, 0, isExport ? "%.*H" : "%.*G", precision, v);
303 m_buf->append(buf);
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) {
307 m_buf->append(".0");
309 free(buf);
311 break;
312 case Type::VarDump:
313 case Type::DebugDump:
315 char *buf;
316 vspprintf(&buf, 0, "float(%.*G)", precision, v);
317 indent();
318 m_buf->append(buf);
319 free(buf);
320 writeRefCount();
321 m_buf->append('\n');
323 break;
324 case Type::Serialize:
325 case Type::Internal:
326 case Type::APCSerialize:
327 case Type::DebuggerSerialize:
328 m_buf->append("d:");
329 if (std::isnan(v)) {
330 m_buf->append("NAN");
331 } else if (std::isinf(v)) {
332 if (v < 0) m_buf->append('-');
333 m_buf->append("INF");
334 } else {
335 char *buf;
336 vspprintf(&buf, 0, "%.*H", serde_precision, v);
337 m_buf->append(buf);
338 free(buf);
340 m_buf->append(';');
341 break;
342 default:
343 assert(false);
344 break;
348 uint16_t reverse16(uint16_t us) {
349 return
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,
375 const char *s,
376 int len,
377 int options) {
378 if (len == 0) {
379 sb.append("\"\"", 2);
380 return;
383 static const char digits[] = "0123456789abcdef";
385 auto const start = sb.size();
386 sb.append('"');
388 // Do a fast path for ASCII characters that don't need escaping
389 int pos = 0;
390 do {
391 int c = s[pos];
392 if (UNLIKELY((unsigned char)c >= 128 || !jsonNoEscape[c])) {
393 goto utf8_decode;
395 sb.append((char)c);
396 pos++;
397 } while (pos < len);
398 sb.append('"');
399 return;
401 utf8_decode:
402 UTF8To16Decoder decoder(s + pos, len - pos, options & k_JSON_FB_LOOSE);
403 for (;;) {
404 int c = options & k_JSON_UNESCAPED_UNICODE ? decoder.decodeAsUTF8()
405 : decoder.decode();
406 if (c == UTF8_END) {
407 sb.append('"');
408 break;
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.
413 sb.resize(start);
414 sb.append("null", 4);
415 break;
417 assert(c >= 0);
418 unsigned short us = (unsigned short)c;
419 switch (us) {
420 case '"':
421 if (options & k_JSON_HEX_QUOT) {
422 sb.append("\\u0022", 6);
423 } else {
424 sb.append("\\\"", 2);
426 break;
427 case '\\': sb.append("\\\\", 2); break;
428 case '/':
429 if (options & k_JSON_UNESCAPED_SLASHES) {
430 sb.append('/');
431 } else {
432 sb.append("\\/", 2);
434 break;
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;
440 case '<':
441 if (options & k_JSON_HEX_TAG || options & k_JSON_FB_EXTRA_ESCAPES) {
442 sb.append("\\u003C", 6);
443 } else {
444 sb.append('<');
446 break;
447 case '>':
448 if (options & k_JSON_HEX_TAG) {
449 sb.append("\\u003E", 6);
450 } else {
451 sb.append('>');
453 break;
454 case '&':
455 if (options & k_JSON_HEX_AMP) {
456 sb.append("\\u0026", 6);
457 } else {
458 sb.append('&');
460 break;
461 case '\'':
462 if (options & k_JSON_HEX_APOS) {
463 sb.append("\\u0027", 6);
464 } else {
465 sb.append('\'');
467 break;
468 case '@':
469 if (options & k_JSON_FB_EXTRA_ESCAPES) {
470 sb.append("\\u0040", 6);
471 } else {
472 sb.append('@');
474 break;
475 case '%':
476 if (options & k_JSON_FB_EXTRA_ESCAPES) {
477 sb.append("\\u0025", 6);
478 } else {
479 sb.append('%');
481 break;
482 default:
483 if (us >= ' ' &&
484 ((options & k_JSON_UNESCAPED_UNICODE) || (us & 127) == us)) {
485 sb.append((char)us);
486 } else {
487 sb.append("\\u", 2);
488 us = reverse16(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)]);
494 break;
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);
505 switch (m_type) {
506 case Type::PrintR: {
507 m_buf->append(v, len);
508 break;
510 case Type::VarExport: {
511 m_buf->append('\'');
512 const char *p = v;
513 for (int i = 0; i < len; i++, p++) {
514 const char c = *p;
515 // adapted from Zend php_var_export and php_addcslashes
516 if (c == '\0') {
517 m_buf->append("' . \"\\0\" . '");
518 continue;
519 } else if (c == '\'' || c == '\\') {
520 m_buf->append('\\');
522 m_buf->append(c);
524 m_buf->append('\'');
525 break;
527 case Type::VarDump:
528 case Type::DebugDump: {
529 indent();
530 m_buf->append("string(");
531 m_buf->append(len);
532 m_buf->append(") \"");
533 m_buf->append(v, len);
534 m_buf->append('"');
535 writeRefCount();
536 m_buf->append('\n');
537 break;
539 case Type::Serialize:
540 case Type::Internal:
541 case Type::APCSerialize:
542 case Type::DebuggerSerialize:
543 m_buf->append("s:");
544 m_buf->append(len);
545 m_buf->append(":\"");
546 m_buf->append(v, len);
547 m_buf->append("\";");
548 break;
549 case Type::JSON: {
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);
553 if (isIntType(dt)) {
554 write(lval);
555 return;
556 } else if (isDoubleType(dt)) {
557 write(dval);
558 return;
561 appendJsonEscape(*m_buf, v, len, m_option);
562 break;
564 case Type::DebuggerDump:
565 case Type::PHPOutput: {
566 if (!noQuotes)
567 m_buf->append('"');
568 for (int i = 0; i < len; ++i) {
569 const unsigned char c = v[i];
570 switch (c) {
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;
577 default: {
578 if (c >= ' ' && c <= '~') {
579 // The range [' ', '~'] contains only printable characters
580 // and we've already handled special cases above
581 m_buf->append(c);
582 } else {
583 char buf[5];
584 snprintf(buf, sizeof(buf), "\\%03o", c);
585 m_buf->append(buf);
590 if (!noQuotes)
591 m_buf->append('"');
592 break;
594 default:
595 assert(false);
596 break;
600 void VariableSerializer::write(const String& v) {
601 if (m_type == Type::APCSerialize && !v.isNull() && v.get()->isStatic()) {
602 union {
603 char buf[8];
604 StringData *sd;
605 } u;
606 u.sd = v.get();
607 m_buf->append("S:");
608 m_buf->append(u.buf, 8);
609 m_buf->append(';');
610 } else {
611 serializeString(v);
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]() {
625 write(ret);
627 } else {
628 // Don't need to check for overflows if ret is of primitive type
629 // because the depth does not change.
630 write(ret);
632 return;
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);
645 return;
647 m_buf->append("{}");
648 } else {
649 auto props = v->toArray(true);
650 pushObjectInfo(v->getClassName(), v->getId(), 'O');
651 serializeArray(props);
652 popObjectInfo();
655 } else {
656 serializeObject(v);
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)) {
664 writeOverflow(tv);
665 } else {
666 func();
668 decNestedLevel(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()) {
677 write(v.toObject());
678 return;
680 serializeVariant(v, isArrayKey);
683 void VariableSerializer::writeNull() {
684 switch (m_type) {
685 case Type::PrintR:
686 // do nothing
687 break;
688 case Type::VarExport:
689 case Type::PHPOutput:
690 m_buf->append("NULL");
691 break;
692 case Type::VarDump:
693 case Type::DebugDump:
694 indent();
695 m_buf->append("NULL");
696 writeRefCount();
697 m_buf->append('\n');
698 break;
699 case Type::Serialize:
700 case Type::Internal:
701 case Type::APCSerialize:
702 case Type::DebuggerSerialize:
703 m_buf->append("N;");
704 break;
705 case Type::JSON:
706 case Type::DebuggerDump:
707 m_buf->append("null");
708 break;
709 default:
710 assert(false);
711 break;
715 void VariableSerializer::writeOverflow(const TypedValue& tv) {
716 bool wasRef = m_referenced;
717 setReferenced(false);
718 switch (m_type) {
719 case Type::PrintR:
720 if (!m_objClass.empty()) {
721 m_buf->append(m_objClass);
722 m_buf->append(" Object\n");
723 } else {
724 m_buf->append("Array\n");
726 m_buf->append(" *RECURSION*");
727 break;
728 case Type::VarExport:
729 case Type::PHPOutput:
730 throwNestingException();
731 case Type::VarDump:
732 case Type::DebugDump:
733 case Type::DebuggerDump:
734 indent();
735 m_buf->append("*RECURSION*\n");
736 break;
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);
741 break;
743 // fall through
744 case Type::Serialize:
745 case Type::Internal:
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;
751 if (wasRef) {
752 m_buf->append("R:");
753 m_buf->append(optId);
754 m_buf->append(';');
755 } else if (isObject) {
756 m_buf->append("r:");
757 m_buf->append(optId);
758 m_buf->append(';');
759 } else {
760 m_buf->append("N;");
763 break;
764 case Type::JSON:
765 json_set_last_error_code(json_error_codes::JSON_ERROR_RECURSION);
766 m_buf->append("null");
767 break;
768 default:
769 assert(false);
770 break;
774 void VariableSerializer::writeRefCount() {
775 if (m_type == Type::DebugDump) {
776 m_buf->append(" refcount(");
777 m_buf->append(m_refCount);
778 m_buf->append(')');
779 m_refCount = 1;
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;
789 info.size = size;
791 switch (m_type) {
792 case Type::DebuggerDump:
793 case Type::PrintR:
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);
801 break;
802 } else if (!m_objClass.empty()) {
803 m_buf->append(m_objClass);
804 m_buf->append(" Object\n");
805 } else {
806 switch (kind) {
807 case ArrayKind::Dict:
808 m_buf->append("Dict\n");
809 break;
810 case ArrayKind::Vec:
811 m_buf->append("Vec\n");
812 break;
813 case ArrayKind::Keyset:
814 m_buf->append("Keyset\n");
815 break;
816 case ArrayKind::PHP:
817 case ArrayKind::VArray:
818 case ArrayKind::DArray:
819 m_buf->append("Array\n");
820 break;
823 if (m_indent > 0) {
824 m_indent += 4;
825 indent();
827 m_buf->append("(\n");
828 m_indent += (info.indent_delta = 4);
829 break;
830 case Type::VarExport:
831 case Type::PHPOutput:
832 if (m_indent > 0 && m_rsrcName.empty()) {
833 m_buf->append('\n');
834 indent();
836 if (!m_objClass.empty()) {
837 m_buf->append(m_objClass);
838 if (m_objCode == 'O') {
839 m_buf->append("::__set_state(array(\n");
840 } else {
841 assert(m_objCode == 'V' || m_objCode == 'K');
842 m_buf->append(" {\n");
844 } else if (!m_rsrcName.empty()) {
845 m_buf->append("NULL");
846 } else {
847 switch (kind) {
848 case ArrayKind::Dict:
849 m_buf->append("dict [\n");
850 break;
851 case ArrayKind::Vec:
852 m_buf->append("vec [\n");
853 break;
854 case ArrayKind::Keyset:
855 m_buf->append("keyset [\n");
856 break;
857 case ArrayKind::PHP:
858 case ArrayKind::VArray:
859 case ArrayKind::DArray:
860 m_buf->append("array (\n");
861 break;
864 m_indent += (info.indent_delta = 2);
865 break;
866 case Type::VarDump:
867 case Type::DebugDump:
868 indent();
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");
875 break;
876 } else if (!m_objClass.empty()) {
877 m_buf->append("object(");
878 m_buf->append(m_objClass);
879 m_buf->append(")#");
880 m_buf->append(m_objId);
881 m_buf->append(' ');
882 } else {
883 switch (kind) {
884 case ArrayKind::Dict:
885 m_buf->append("dict");
886 break;
887 case ArrayKind::Vec:
888 m_buf->append("vec");
889 break;
890 case ArrayKind::Keyset:
891 m_buf->append("keyset");
892 break;
893 case ArrayKind::PHP:
894 case ArrayKind::VArray:
895 case ArrayKind::DArray:
896 m_buf->append("array");
897 break;
900 m_buf->append('(');
901 m_buf->append(size);
902 m_buf->append(')');
904 // ...so to strictly follow PHP's output
905 if (m_type == Type::VarDump) {
906 m_buf->append(' ');
907 } else {
908 writeRefCount();
911 m_buf->append("{\n");
912 m_indent += (info.indent_delta = 2);
913 break;
914 case Type::Serialize:
915 case Type::Internal:
916 case Type::APCSerialize:
917 case Type::DebuggerSerialize:
918 if (!m_rsrcName.empty() && m_type == Type::DebuggerSerialize) {
919 m_buf->append("L:");
920 m_buf->append(m_rsrcId);
921 m_buf->append(":");
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);
928 m_buf->append(":");
929 m_buf->append((int)m_objClass.size());
930 m_buf->append(":\"");
931 m_buf->append(m_objClass);
932 m_buf->append("\":");
933 m_buf->append(size);
934 m_buf->append(":{");
935 } else {
936 switch (kind) {
937 case ArrayKind::Dict:
938 m_buf->append("D:");
939 break;
940 case ArrayKind::Vec:
941 m_buf->append("v:");
942 break;
943 case ArrayKind::Keyset:
944 m_buf->append("k:");
945 break;
946 case ArrayKind::PHP:
947 m_buf->append("a:");
948 break;
949 case ArrayKind::VArray:
950 m_buf->append("y:");
951 break;
952 case ArrayKind::DArray:
953 m_buf->append("Y:");
954 break;
956 m_buf->append(size);
957 m_buf->append(":{");
959 break;
960 case Type::JSON:
961 info.is_vector =
962 (m_objClass.empty() || m_objCode == 'V' || m_objCode == 'K') &&
963 isVectorData &&
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) {
972 m_buf->append('[');
973 } else {
974 m_buf->append('{');
977 if (m_type == Type::JSON && (m_option & k_JSON_PRETTY_PRINT) &&
978 info.size > 0) {
979 m_buf->append("\n");
980 m_indent += (info.indent_delta = 4);
983 break;
984 default:
985 assert(false);
986 break;
989 // ...so we don't mess up next array output
990 if (!m_objClass.empty() || !m_rsrcName.empty()) {
991 m_objClass.clear();
992 info.is_object = true;
993 } else {
994 info.is_object = false;
998 void VariableSerializer::writePropertyKey(const String& prop) {
999 const char *key = prop.data();
1000 int kl = prop.size();
1001 if (!*key && kl) {
1002 const char *cls = key + 1;
1003 if (*cls == '*') {
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);
1009 } else {
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);
1018 } else {
1019 m_buf->append(prop);
1020 if (m_type != Type::PrintR && m_type != Type::DebuggerDump) {
1021 m_buf->append('"');
1026 /* key MUST be a non-reference string or int */
1027 void VariableSerializer::writeArrayKey(
1028 const Variant& key,
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();
1037 switch (m_type) {
1038 case Type::DebuggerDump:
1039 case Type::PrintR: {
1040 indent();
1041 if (kind == AK::Keyset) return;
1042 m_buf->append('[');
1043 if (info.is_object && skey) {
1044 writePropertyKey(String{keyCell->m_data.pstr});
1045 } else {
1046 m_buf->append(key);
1048 m_buf->append("] => ");
1049 break;
1052 case Type::VarExport:
1053 case Type::PHPOutput:
1054 indent();
1055 if (kind == AK::Vec || kind == AK::Keyset) return;
1056 write(key, true);
1057 m_buf->append(" => ");
1058 break;
1060 case Type::VarDump:
1061 case Type::DebugDump:
1062 if (kind == AK::Vec || kind == AK::Keyset) return;
1063 indent();
1064 m_buf->append('[');
1065 if (!skey) {
1066 m_buf->append(keyCell->m_data.num);
1067 } else {
1068 m_buf->append('"');
1069 if (info.is_object) {
1070 writePropertyKey(String{keyCell->m_data.pstr});
1071 } else {
1072 m_buf->append(keyCell->m_data.pstr);
1073 m_buf->append('"');
1076 m_buf->append("]=>\n");
1077 break;
1079 case Type::APCSerialize:
1080 if (kind == AK::Vec || kind == AK::Keyset || kind == AK::VArray) return;
1081 if (skey) {
1082 write(StrNR(keyCell->m_data.pstr).asString());
1083 return;
1086 case Type::Serialize:
1087 case Type::Internal:
1088 case Type::DebuggerSerialize:
1089 if (kind == AK::Vec || kind == AK::Keyset || kind == AK::VArray) return;
1090 write(key);
1091 break;
1093 case Type::JSON:
1094 if (!info.is_vector && kind != ArrayKind::Keyset) {
1095 if (!info.first_element) {
1096 m_buf->append(',');
1098 if (UNLIKELY(m_option & k_JSON_PRETTY_PRINT)) {
1099 if (!info.first_element) {
1100 m_buf->append("\n");
1102 indent();
1104 if (skey) {
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) {
1109 while (*++k) len--;
1110 k++;
1111 len -= 2;
1113 write(k, len, true);
1114 } else {
1115 m_buf->append('"');
1116 m_buf->append(keyCell->m_data.num);
1117 m_buf->append('"');
1119 m_buf->append(':');
1120 if (UNLIKELY(m_option & k_JSON_PRETTY_PRINT)) {
1121 m_buf->append(' ');
1124 break;
1126 default:
1127 assert(false);
1128 break;
1132 void VariableSerializer::writeCollectionKey(
1133 const Variant& key,
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) {
1140 m_valueCount++;
1142 writeArrayKey(key, kind);
1145 void VariableSerializer::writeArrayValue(
1146 const Variant& value,
1147 VariableSerializer::ArrayKind kind
1149 switch (m_type) {
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)) {
1157 m_valueCount++;
1159 write(value);
1160 break;
1162 case Type::DebuggerDump:
1163 case Type::PrintR:
1164 write(value);
1165 m_buf->append('\n');
1166 break;
1168 case Type::VarExport:
1169 case Type::PHPOutput:
1170 write(value);
1171 m_buf->append(",\n");
1172 break;
1174 case Type::JSON: {
1175 ArrayInfo &info = m_arrayInfos.back();
1176 if (info.is_vector || kind == ArrayKind::Keyset) {
1177 if (!info.first_element) {
1178 m_buf->append(',');
1180 if (UNLIKELY(m_option & k_JSON_PRETTY_PRINT)) {
1181 if (!info.first_element) {
1182 m_buf->append("\n");
1184 indent();
1187 write(value);
1188 break;
1191 default:
1192 write(value);
1193 break;
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;
1206 switch (m_type) {
1207 case Type::DebuggerDump:
1208 case Type::PrintR:
1209 if (m_rsrcName.empty()) {
1210 indent();
1211 m_buf->append(")\n");
1212 if (m_indent > 0) {
1213 m_indent -= 4;
1216 break;
1217 case Type::VarExport:
1218 case Type::PHPOutput:
1219 if (m_rsrcName.empty()) {
1220 indent();
1222 if (info.is_object && m_objCode) {
1223 if (m_objCode == 'O') {
1224 m_buf->append("))");
1225 } else {
1226 assert(m_objCode == 'V' || m_objCode == 'K');
1227 m_buf->append("}");
1229 } else if (m_rsrcName.empty()) { // for rsrc, only write NULL in arrayHeader
1230 switch (kind) {
1231 case ArrayKind::Dict:
1232 case ArrayKind::Vec:
1233 case ArrayKind::Keyset:
1234 m_buf->append("]");
1235 break;
1236 case ArrayKind::PHP:
1237 case ArrayKind::VArray:
1238 case ArrayKind::DArray:
1239 m_buf->append(')');
1240 break;
1243 break;
1244 case Type::VarDump:
1245 case Type::DebugDump:
1246 if (m_rsrcName.empty()) {
1247 indent();
1248 m_buf->append("}\n");
1250 break;
1251 case Type::Serialize:
1252 case Type::Internal:
1253 case Type::APCSerialize:
1254 case Type::DebuggerSerialize:
1255 m_buf->append('}');
1256 break;
1257 case Type::JSON:
1258 if (m_type == Type::JSON && (m_option & k_JSON_PRETTY_PRINT) &&
1259 info.size > 0) {
1260 m_buf->append("\n");
1261 indent();
1263 if (info.is_vector || kind == ArrayKind::Keyset) {
1264 m_buf->append(']');
1265 } else {
1266 m_buf->append('}');
1268 break;
1269 default:
1270 assert(false);
1271 break;
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());
1287 m_buf->append('}');
1290 ///////////////////////////////////////////////////////////////////////////////
1292 void VariableSerializer::indent() {
1293 for (int i = 0; i < m_indent; i++) {
1294 m_buf->append(' ');
1296 if (m_referenced) {
1297 if (m_indent > 0 && (m_type == Type::VarDump ||
1298 m_type == Type::DebugDump)) {
1299 m_buf->append('&');
1301 m_referenced = false;
1305 bool VariableSerializer::incNestedLevel(const TypedValue& tv) {
1306 ++m_currentDepth;
1308 switch (m_type) {
1309 case Type::VarExport:
1310 case Type::PHPOutput:
1311 case Type::PrintR:
1312 case Type::VarDump:
1313 case Type::DebugDump:
1314 case Type::DebuggerDump:
1315 return ++m_refs[tv].m_count >= m_maxCount;
1316 case Type::JSON:
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) {
1323 return true;
1325 // fall through
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)) {
1334 return true;
1336 ref.m_id = m_valueCount;
1337 return ct >= (m_maxCount - 1);
1339 break;
1340 default:
1341 assert(false);
1342 break;
1344 return false;
1347 void VariableSerializer::decNestedLevel(const TypedValue& tv) {
1348 --m_currentDepth;
1349 --m_refs[tv].m_count;
1350 if (m_type == Type::DebuggerSerialize && m_maxLevelDebugger > 0) {
1351 --m_levelDebugger;
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)) {
1363 writeOverflow(*tv);
1364 } else {
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);
1369 } else {
1370 serializeVariant(*tv->m_data.pref->var(), isArrayKey);
1374 NEVER_INLINE
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) {
1382 case KindOfUninit:
1383 case KindOfNull:
1384 assert(!isArrayKey);
1385 writeNull();
1386 return;
1388 case KindOfBoolean:
1389 assert(!isArrayKey);
1390 write(tv->m_data.num != 0);
1391 return;
1393 case KindOfInt64:
1394 write(tv->m_data.num);
1395 return;
1397 case KindOfDouble:
1398 write(tv->m_data.dbl);
1399 return;
1401 case KindOfPersistentString:
1402 case KindOfString:
1403 write(tv->m_data.pstr->data(),
1404 tv->m_data.pstr->size(), isArrayKey, noQuotes);
1405 return;
1407 case KindOfPersistentVec:
1408 case KindOfVec:
1409 assert(!isArrayKey);
1410 assert(tv->m_data.parr->isVecArray());
1411 serializeArray(tv->m_data.parr, skipNestCheck);
1412 return;
1414 case KindOfPersistentDict:
1415 case KindOfDict:
1416 assert(!isArrayKey);
1417 assert(tv->m_data.parr->isDict());
1418 serializeArray(tv->m_data.parr, skipNestCheck);
1419 return;
1421 case KindOfPersistentKeyset:
1422 case KindOfKeyset:
1423 assert(!isArrayKey);
1424 assert(tv->m_data.parr->isKeyset());
1425 serializeArray(tv->m_data.parr, skipNestCheck);
1426 return;
1428 case KindOfPersistentArray:
1429 case KindOfArray:
1430 assert(!isArrayKey);
1431 assert(tv->m_data.parr->isPHPArray());
1432 serializeArray(tv->m_data.parr, skipNestCheck);
1433 return;
1435 case KindOfObject:
1436 assert(!isArrayKey);
1437 serializeObject(tv->m_data.pobj);
1438 return;
1440 case KindOfResource:
1441 assert(!isArrayKey);
1442 serializeResource(tv->m_data.pres->data());
1443 return;
1445 case KindOfRef:
1446 serializeRef(tv, isArrayKey);
1447 return;
1449 not_reached();
1452 void VariableSerializer::serializeResourceImpl(const ResourceData* res) {
1453 pushResourceInfo(res->o_getResourceName(), res->getId());
1454 serializeArray(empty_array());
1455 popResourceInfo();
1458 void VariableSerializer::serializeResource(const ResourceData* res) {
1459 TypedValue tv = make_tv<KindOfResource>(const_cast<ResourceHdr*>(res->hdr()));
1460 if (UNLIKELY(incNestedLevel(tv))) {
1461 writeOverflow(tv);
1462 } else if (auto trace = dynamic_cast<const CompactTrace*>(res)) {
1463 serializeArray(trace->extract());
1464 } else {
1465 serializeResourceImpl(res);
1467 decNestedLevel(tv);
1470 void VariableSerializer::serializeString(const String& str) {
1471 if (str) {
1472 write(str.data(), str.size());
1473 } else {
1474 writeNull();
1478 void VariableSerializer::serializeArrayImpl(const ArrayData* arr) {
1479 using AK = VariableSerializer::ArrayKind;
1480 AK kind = getKind(arr);
1481 writeArrayHeader(
1482 arr->size(),
1483 arr->isVectorData(),
1484 kind
1487 IterateKV(
1488 arr,
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));
1503 return;
1505 if (!skipNestCheck) {
1506 TypedValue tv = make_array_like_tv(const_cast<ArrayData*>(arr));
1507 if (incNestedLevel(tv)) {
1508 writeOverflow(tv);
1509 } else {
1510 serializeArrayImpl(arr);
1512 decNestedLevel(tv);
1513 } else {
1514 // If isObject, the array is temporary and we should not check or save
1515 // its pointer.
1516 serializeArrayImpl(arr);
1520 void VariableSerializer::serializeArray(const Array& arr,
1521 bool isObject /* = false */) {
1522 if (!arr.isNull()) {
1523 serializeArray(arr.get(), isObject);
1524 } else {
1525 writeNull();
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);
1543 } else {
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) {
1563 indent();
1565 writeArrayValue(iter.second(), AK::PHP);
1567 } else {
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) {
1574 indent();
1576 writeArrayValue(iter.second(), AK::PHP);
1578 } else {
1579 for (ArrayIter iter(obj); iter; ++iter) {
1580 writeCollectionKey(iter.first(), AK::PHP);
1581 writeArrayValue(iter.second(), AK::PHP);
1585 writeArrayFooter(AK::PHP);
1587 popObjectInfo();
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) {
1602 int sizeToCut = 0;
1603 do {
1604 sizeToCut++;
1605 } while (key[sizeToCut] != '\0');
1606 key = key.substr(sizeToCut+1);
1608 props.setWithRef(key, iter.secondVal());
1610 return props;
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());
1618 if (!debuginfo) {
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);
1624 return 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);
1632 return 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();
1647 if (ret.isNull()) {
1648 return empty_array();
1650 raise_error("__debugInfo() must return an array");
1651 not_reached();
1654 void VariableSerializer::serializeObjectImpl(const ObjectData* obj) {
1655 bool handleSleep = false;
1656 Variant serializableNativeData = init_null();
1657 Variant ret;
1658 auto const type = getType();
1660 if (obj->isCollection()) {
1661 serializeCollection(const_cast<ObjectData*>(obj));
1662 return;
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());
1670 ret =
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()) {
1675 writeNull();
1676 } else {
1677 raise_error("%s::serialize() must return a string or NULL",
1678 obj->getClassName().data());
1680 return;
1682 // Only serialize CPP extension type instances which can actually
1683 // be deserialized. Otherwise, raise a warning and serialize
1684 // null.
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);
1694 return;
1696 if (obj->getAttribute(ObjectData::HasSleep)) {
1697 handleSleep = true;
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());
1711 return;
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;
1729 if (subLen > 2) {
1730 if (subLen == 3 && memberName.data()[1] == '*') {
1731 attrMask = AttrProtected;
1732 memberName = memberName.substr(subLen);
1733 } else {
1734 attrMask = AttrPrivate;
1735 String cls = memberName.substr(1, subLen - 2);
1736 ctx = Unit::lookupClass(cls.get());
1737 if (ctx) {
1738 memberName = memberName.substr(subLen);
1739 } else {
1740 ctx = obj_cls;
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());
1761 continue;
1765 if (!attrMask &&
1766 UNLIKELY(obj->getAttribute(ObjectData::HasDynPropArr))) {
1767 if (auto const prop = obj->dynPropArray()->rval(propName.get())) {
1768 wanted.set(propName, prop.tv());
1769 continue;
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);
1781 popObjectInfo();
1782 } else {
1783 raise_notice("serialize(): __sleep should return an array only "
1784 "containing the names of instance-variables to "
1785 "serialize");
1786 serializeVariant(uninit_null());
1788 } else {
1789 if (type == VariableSerializer::Type::VarExport &&
1790 obj->instanceof(c_Closure::classof())) {
1791 write(obj->getClassName());
1792 } else {
1793 auto className = obj->getClassName();
1794 Array properties = getSerializeProps(obj);
1795 if (type == VariableSerializer::Type::DebuggerSerialize) {
1796 try {
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);
1802 } catch (...) {
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());
1811 return;
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);
1818 return;
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);
1824 return;
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(
1834 nullptr,
1835 s_PHP_Incomplete_Class_Name.get()
1836 ).unboxed();
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);
1841 popObjectInfo();
1842 return;
1845 pushObjectInfo(className, obj->getId(), 'O');
1846 if (!serializableNativeData.isNull()) {
1847 properties.set(s_serializedNativeDataKey, serializableNativeData);
1849 serializeArray(properties, true);
1850 popObjectInfo();
1855 void VariableSerializer::serializeObject(const ObjectData* obj) {
1856 TypedValue tv = make_tv<KindOfObject>(const_cast<ObjectData*>(obj));
1857 if (UNLIKELY(incNestedLevel(tv))) {
1858 writeOverflow(tv);
1859 } else {
1860 serializeObjectImpl(obj);
1862 decNestedLevel(tv);
1865 void VariableSerializer::serializeObject(const Object& obj) {
1866 if (obj) {
1867 serializeObject(obj.get());
1868 } else {
1869 writeNull();