942 linhas
23 KiB
C++
942 linhas
23 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| http://www.php.net/license/3_01.txt |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include <runtime/base/variable_serializer.h>
|
|
#include <runtime/base/execution_context.h>
|
|
#include <runtime/base/complex_types.h>
|
|
#include <util/exception.h>
|
|
#include <runtime/base/zend/zend_printf.h>
|
|
#include <runtime/base/zend/zend_functions.h>
|
|
#include <runtime/base/zend/zend_string.h>
|
|
#include <math.h>
|
|
#include <cmath>
|
|
#include <runtime/base/runtime_option.h>
|
|
#include <runtime/base/array/array_iterator.h>
|
|
#include <runtime/base/util/request_local.h>
|
|
#include <runtime/ext/ext_json.h>
|
|
#include <runtime/ext/ext_collections.h>
|
|
|
|
namespace HPHP {
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// static strings
|
|
|
|
static StaticString s_JsonSerializable("JsonSerializable");
|
|
static StaticString s_jsonSerialize("jsonSerialize");
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
VariableSerializer::VariableSerializer(Type type, int option /* = 0 */,
|
|
int maxRecur /* = 3 */)
|
|
: m_type(type), m_option(option), m_buf(nullptr), m_indent(0),
|
|
m_valueCount(0), m_referenced(false), m_refCount(1), m_maxCount(maxRecur),
|
|
m_levelDebugger(0) {
|
|
m_maxLevelDebugger = g_context->getDebuggerPrintLevel();
|
|
if (type == Serialize || type == APCSerialize || type == DebuggerSerialize) {
|
|
m_arrayIds = new SmartPtrCtrMap();
|
|
} else {
|
|
m_arrayIds = nullptr;
|
|
}
|
|
}
|
|
|
|
void VariableSerializer::setObjectInfo(CStrRef objClass, int objId,
|
|
char objCode) {
|
|
assert(objCode == 'O' || objCode == 'V' || objCode == 'K');
|
|
m_objClass = objClass;
|
|
m_objId = objId;
|
|
m_objCode = objCode;
|
|
}
|
|
|
|
void VariableSerializer::getResourceInfo(String &rsrcName, int &rsrcId) {
|
|
rsrcName = m_rsrcName;
|
|
rsrcId = m_rsrcId;
|
|
}
|
|
|
|
void VariableSerializer::setResourceInfo(CStrRef rsrcName, int rsrcId) {
|
|
m_rsrcName = rsrcName;
|
|
m_rsrcId = rsrcId;
|
|
}
|
|
|
|
String VariableSerializer::serialize(CVarRef v, bool ret) {
|
|
StringBuffer buf;
|
|
m_buf = &buf;
|
|
if (ret) {
|
|
buf.setOutputLimit(RuntimeOption::SerializationSizeLimit);
|
|
} else {
|
|
buf.setOutputLimit(StringData::MaxSize);
|
|
}
|
|
m_valueCount = 1;
|
|
write(v);
|
|
if (ret) {
|
|
return m_buf->detach();
|
|
} else {
|
|
String str = m_buf->detach();
|
|
g_context->write(str);
|
|
}
|
|
return null_string;
|
|
}
|
|
|
|
String VariableSerializer::serializeValue(CVarRef v, bool limit) {
|
|
StringBuffer buf;
|
|
m_buf = &buf;
|
|
if (limit) {
|
|
buf.setOutputLimit(RuntimeOption::SerializationSizeLimit);
|
|
}
|
|
m_valueCount = 1;
|
|
write(v);
|
|
return m_buf->detach();
|
|
}
|
|
|
|
String VariableSerializer::serializeWithLimit(CVarRef v, int limit) {
|
|
if (m_type == Serialize || m_type == JSON || m_type == APCSerialize ||
|
|
m_type == DebuggerSerialize) {
|
|
assert(false);
|
|
return null_string;
|
|
}
|
|
StringBuffer buf;
|
|
m_buf = &buf;
|
|
if (RuntimeOption::SerializationSizeLimit > 0 &&
|
|
(limit <= 0 || limit > RuntimeOption::SerializationSizeLimit)) {
|
|
limit = RuntimeOption::SerializationSizeLimit;
|
|
}
|
|
buf.setOutputLimit(limit);
|
|
//Does not need m_valueCount, which is only useful with the unsupported types
|
|
try {
|
|
write(v);
|
|
} catch (StringBufferLimitException &e) {
|
|
return e.m_result;
|
|
}
|
|
return m_buf->detach();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void VariableSerializer::write(bool v) {
|
|
switch (m_type) {
|
|
case PrintR:
|
|
if (v) m_buf->append(1);
|
|
break;
|
|
case VarExport:
|
|
case PHPOutput:
|
|
case JSON:
|
|
case DebuggerDump:
|
|
m_buf->append(v ? "true" : "false");
|
|
break;
|
|
case VarDump:
|
|
case DebugDump:
|
|
indent();
|
|
m_buf->append(v ? "bool(true)" : "bool(false)");
|
|
writeRefCount();
|
|
m_buf->append('\n');
|
|
break;
|
|
case Serialize:
|
|
case APCSerialize:
|
|
case DebuggerSerialize:
|
|
m_buf->append(v ? "b:1;" : "b:0;");
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void VariableSerializer::write(int64_t v) {
|
|
switch (m_type) {
|
|
case PrintR:
|
|
case VarExport:
|
|
case PHPOutput:
|
|
case JSON:
|
|
case DebuggerDump:
|
|
m_buf->append(v);
|
|
break;
|
|
case VarDump:
|
|
indent();
|
|
m_buf->append("int(");
|
|
m_buf->append(v);
|
|
m_buf->append(")\n");
|
|
break;
|
|
case DebugDump:
|
|
indent();
|
|
m_buf->append("long(");
|
|
m_buf->append(v);
|
|
m_buf->append(')');
|
|
writeRefCount();
|
|
m_buf->append('\n');
|
|
break;
|
|
case Serialize:
|
|
case APCSerialize:
|
|
case DebuggerSerialize:
|
|
m_buf->append("i:");
|
|
m_buf->append(v);
|
|
m_buf->append(';');
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void VariableSerializer::write(double v) {
|
|
switch (m_type) {
|
|
case JSON:
|
|
if (!std::isinf(v) && !std::isnan(v)) {
|
|
char *buf;
|
|
if (v == 0.0) v = 0.0; // so to avoid "-0" output
|
|
vspprintf(&buf, 0, "%.*k", 14, v);
|
|
m_buf->append(buf);
|
|
free(buf);
|
|
} else {
|
|
// PHP issues a warning: double INF/NAN does not conform to the
|
|
// JSON spec, encoded as 0.
|
|
m_buf->append('0');
|
|
}
|
|
break;
|
|
case VarExport:
|
|
case PHPOutput:
|
|
case PrintR:
|
|
case DebuggerDump:
|
|
{
|
|
char *buf;
|
|
if (v == 0.0) v = 0.0; // so to avoid "-0" output
|
|
bool isExport = m_type == VarExport || m_type == PHPOutput;
|
|
vspprintf(&buf, 0, isExport ? "%.*H" : "%.*G", 14, v);
|
|
m_buf->append(buf);
|
|
// In PHPOutput mode, we always want doubles to parse as
|
|
// doubles, so make sure there's a decimal point.
|
|
if (m_type == PHPOutput && strpbrk(buf, ".E") == nullptr) {
|
|
m_buf->append(".0");
|
|
}
|
|
free(buf);
|
|
}
|
|
break;
|
|
case VarDump:
|
|
case DebugDump:
|
|
{
|
|
char *buf;
|
|
if (v == 0.0) v = 0.0; // so to avoid "-0" output
|
|
vspprintf(&buf, 0, "float(%.*G)", 14, v);
|
|
indent();
|
|
m_buf->append(buf);
|
|
free(buf);
|
|
writeRefCount();
|
|
m_buf->append('\n');
|
|
}
|
|
break;
|
|
case Serialize:
|
|
case APCSerialize:
|
|
case DebuggerSerialize:
|
|
m_buf->append("d:");
|
|
if (std::isnan(v)) {
|
|
m_buf->append("NAN");
|
|
} else if (std::isinf(v)) {
|
|
if (v < 0) m_buf->append('-');
|
|
m_buf->append("INF");
|
|
} else {
|
|
char *buf;
|
|
if (v == 0.0) v = 0.0; // so to avoid "-0" output
|
|
vspprintf(&buf, 0, "%.*H", 14, v);
|
|
m_buf->append(buf);
|
|
free(buf);
|
|
}
|
|
m_buf->append(';');
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void VariableSerializer::write(const char *v, int len /* = -1 */,
|
|
bool isArrayKey /* = false */) {
|
|
if (v == nullptr) v = "";
|
|
if (len < 0) len = strlen(v);
|
|
|
|
switch (m_type) {
|
|
case PrintR: {
|
|
m_buf->append(v, len);
|
|
break;
|
|
}
|
|
case DebuggerDump:
|
|
case VarExport: {
|
|
m_buf->append('\'');
|
|
const char *p = v;
|
|
for (int i = 0; i < len; i++, p++) {
|
|
const char c = *p;
|
|
// adapted from Zend php_var_export and php_addcslashes
|
|
if (c == '\0') {
|
|
m_buf->append("' . \"\\0\" . '");
|
|
continue;
|
|
} else if (c == '\'' || c == '\\') {
|
|
m_buf->append('\\');
|
|
}
|
|
m_buf->append(c);
|
|
}
|
|
m_buf->append('\'');
|
|
break;
|
|
}
|
|
case VarDump:
|
|
case DebugDump: {
|
|
indent();
|
|
m_buf->append("string(");
|
|
m_buf->append(len);
|
|
m_buf->append(") \"");
|
|
m_buf->append(v, len);
|
|
m_buf->append('"');
|
|
writeRefCount();
|
|
m_buf->append('\n');
|
|
break;
|
|
}
|
|
case Serialize:
|
|
case APCSerialize:
|
|
case DebuggerSerialize:
|
|
m_buf->append("s:");
|
|
m_buf->append(len);
|
|
m_buf->append(":\"");
|
|
m_buf->append(v, len);
|
|
m_buf->append("\";");
|
|
break;
|
|
case JSON: {
|
|
if (m_option & k_JSON_NUMERIC_CHECK) {
|
|
int64_t lval; double dval;
|
|
switch (is_numeric_string(v, len, &lval, &dval, 0)) {
|
|
case KindOfInt64:
|
|
write(lval);
|
|
return;
|
|
case KindOfDouble:
|
|
write(dval);
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_buf->appendJsonEscape(v, len, m_option);
|
|
break;
|
|
}
|
|
case PHPOutput: {
|
|
m_buf->append('"');
|
|
for (int i = 0; i < len; ++i) {
|
|
const unsigned char c = v[i];
|
|
switch (c) {
|
|
case '\n': m_buf->append("\\n"); break;
|
|
case '\r': m_buf->append("\\r"); break;
|
|
case '\t': m_buf->append("\\t"); break;
|
|
case '\\': m_buf->append("\\\\"); break;
|
|
case '$': m_buf->append("\\$"); break;
|
|
case '"': m_buf->append("\\\""); break;
|
|
default: {
|
|
if (c >= ' ' && c <= '~') {
|
|
// The range [' ', '~'] contains only printable characters
|
|
// and we've already handled special cases above
|
|
m_buf->append(c);
|
|
} else {
|
|
char buf[5];
|
|
snprintf(buf, sizeof(buf), "\\%03o", c);
|
|
m_buf->append(buf);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
m_buf->append('"');
|
|
break;
|
|
}
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void VariableSerializer::write(CStrRef v) {
|
|
if (m_type == APCSerialize && !v.isNull() && v->isStatic()) {
|
|
union {
|
|
char buf[8];
|
|
StringData *sd;
|
|
} u;
|
|
u.sd = v.get();
|
|
m_buf->append("S:");
|
|
m_buf->append(u.buf, 8);
|
|
m_buf->append(';');
|
|
} else {
|
|
v.serialize(this);
|
|
}
|
|
}
|
|
|
|
void VariableSerializer::write(CObjRef v) {
|
|
if (!v.isNull() && m_type == JSON) {
|
|
|
|
if (v.instanceof(SystemLib::s_JsonSerializableClass)) {
|
|
assert(!v->isCollection());
|
|
Variant ret = v->o_invoke(s_jsonSerialize, null_array, -1);
|
|
// for non objects or when $this is returned
|
|
if (!ret.isObject() || (ret.isObject() && !ret.same(v))) {
|
|
write(ret);
|
|
return;
|
|
}
|
|
}
|
|
if (incNestedLevel(v.get(), true)) {
|
|
writeOverflow(v.get(), true);
|
|
} else {
|
|
if (v->isCollection()) {
|
|
collectionSerialize(v.get(), this);
|
|
} else {
|
|
Array props(ArrayData::Create());
|
|
ClassInfo::GetArray(v.get(), props, ClassInfo::GetArrayPublic);
|
|
setObjectInfo(v->o_getClassName(), v->o_getId(), 'O');
|
|
props.serialize(this);
|
|
}
|
|
}
|
|
decNestedLevel(v.get());
|
|
} else {
|
|
v.serialize(this);
|
|
}
|
|
}
|
|
|
|
void VariableSerializer::write(CVarRef v, bool isArrayKey /* = false */) {
|
|
setReferenced(v.isReferenced());
|
|
setRefCount(v.getRefCount());
|
|
if (!isArrayKey && v.isObject()) {
|
|
write(v.toObject());
|
|
return;
|
|
}
|
|
v.serialize(this, isArrayKey);
|
|
}
|
|
|
|
void VariableSerializer::writeNull() {
|
|
switch (m_type) {
|
|
case PrintR:
|
|
// do nothing
|
|
break;
|
|
case VarExport:
|
|
case PHPOutput:
|
|
m_buf->append("NULL");
|
|
break;
|
|
case VarDump:
|
|
case DebugDump:
|
|
indent();
|
|
m_buf->append("NULL");
|
|
writeRefCount();
|
|
m_buf->append('\n');
|
|
break;
|
|
case Serialize:
|
|
case APCSerialize:
|
|
case DebuggerSerialize:
|
|
m_buf->append("N;");
|
|
break;
|
|
case JSON:
|
|
case DebuggerDump:
|
|
m_buf->append("null");
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void VariableSerializer::writeOverflow(void* ptr, bool isObject /* = false */) {
|
|
bool wasRef = m_referenced;
|
|
setReferenced(false);
|
|
switch (m_type) {
|
|
case PrintR:
|
|
if (!m_objClass.empty()) {
|
|
m_buf->append(m_objClass);
|
|
m_buf->append(" Object\n");
|
|
} else {
|
|
m_buf->append("Array\n");
|
|
}
|
|
m_buf->append(" *RECURSION*");
|
|
break;
|
|
case VarExport:
|
|
case PHPOutput:
|
|
throw NestingLevelTooDeepException();
|
|
case VarDump:
|
|
case DebugDump:
|
|
case DebuggerDump:
|
|
indent();
|
|
m_buf->append("*RECURSION*\n");
|
|
break;
|
|
case DebuggerSerialize:
|
|
if (m_maxLevelDebugger > 0 && m_levelDebugger > m_maxLevelDebugger) {
|
|
// Not recursion, just cut short of print
|
|
m_buf->append("s:12:\"...(omitted)\";", 20);
|
|
break;
|
|
}
|
|
// fall through
|
|
case Serialize:
|
|
case APCSerialize:
|
|
{
|
|
assert(m_arrayIds);
|
|
SmartPtrCtrMap::const_iterator iter = m_arrayIds->find(ptr);
|
|
assert(iter != m_arrayIds->end());
|
|
int id = iter->second;
|
|
if (isObject) {
|
|
m_buf->append("r:");
|
|
m_buf->append(id);
|
|
m_buf->append(';');
|
|
} else if (wasRef) {
|
|
m_buf->append("R:");
|
|
m_buf->append(id);
|
|
m_buf->append(';');
|
|
} else {
|
|
m_buf->append("N;");
|
|
}
|
|
}
|
|
break;
|
|
case JSON:
|
|
raise_warning("json_encode(): recursion detected");
|
|
m_buf->append("null");
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void VariableSerializer::writeRefCount() {
|
|
if (m_type == DebugDump) {
|
|
m_buf->append(" refcount(");
|
|
m_buf->append(m_refCount);
|
|
m_buf->append(')');
|
|
m_refCount = 1;
|
|
}
|
|
}
|
|
|
|
void VariableSerializer::writeArrayHeader(int size, bool isVectorData) {
|
|
m_arrayInfos.push_back(ArrayInfo());
|
|
ArrayInfo &info = m_arrayInfos.back();
|
|
info.first_element = true;
|
|
info.indent_delta = 0;
|
|
|
|
switch (m_type) {
|
|
case PrintR:
|
|
if (!m_rsrcName.empty()) {
|
|
m_buf->append("Resource id #");
|
|
m_buf->append(m_rsrcId);
|
|
break;
|
|
} else if (!m_objClass.empty()) {
|
|
m_buf->append(m_objClass);
|
|
m_buf->append(" Object\n");
|
|
} else {
|
|
m_buf->append("Array\n");
|
|
}
|
|
if (m_indent > 0) {
|
|
m_indent += 4;
|
|
indent();
|
|
}
|
|
m_buf->append("(\n");
|
|
m_indent += (info.indent_delta = 4);
|
|
break;
|
|
case VarExport:
|
|
case PHPOutput:
|
|
if (m_indent > 0) {
|
|
m_buf->append('\n');
|
|
indent();
|
|
}
|
|
if (!m_objClass.empty()) {
|
|
m_buf->append(m_objClass);
|
|
m_buf->append("::__set_state(array(\n");
|
|
} else {
|
|
m_buf->append("array (\n");
|
|
}
|
|
m_indent += (info.indent_delta = 2);
|
|
break;
|
|
case VarDump:
|
|
case DebugDump:
|
|
indent();
|
|
if (!m_rsrcName.empty()) {
|
|
m_buf->append("resource(");
|
|
m_buf->append(m_rsrcId);
|
|
m_buf->append(") of type (");
|
|
m_buf->append(m_rsrcName);
|
|
m_buf->append(")\n");
|
|
break;
|
|
} else if (!m_objClass.empty()) {
|
|
m_buf->append("object(");
|
|
m_buf->append(m_objClass);
|
|
m_buf->append(")#");
|
|
m_buf->append(m_objId);
|
|
m_buf->append(' ');
|
|
} else {
|
|
m_buf->append("array");
|
|
}
|
|
m_buf->append('(');
|
|
m_buf->append(size);
|
|
m_buf->append(')');
|
|
|
|
// ...so to strictly follow PHP's output
|
|
if (m_type == VarDump) {
|
|
m_buf->append(' ');
|
|
} else {
|
|
writeRefCount();
|
|
}
|
|
|
|
m_buf->append("{\n");
|
|
m_indent += (info.indent_delta = 2);
|
|
break;
|
|
case Serialize:
|
|
case APCSerialize:
|
|
case DebuggerSerialize:
|
|
if (!m_objClass.empty()) {
|
|
m_buf->append(m_objCode);
|
|
m_buf->append(":");
|
|
m_buf->append((int)m_objClass.size());
|
|
m_buf->append(":\"");
|
|
m_buf->append(m_objClass);
|
|
m_buf->append("\":");
|
|
m_buf->append(size);
|
|
m_buf->append(":{");
|
|
} else {
|
|
m_buf->append("a:");
|
|
m_buf->append(size);
|
|
m_buf->append(":{");
|
|
}
|
|
break;
|
|
case JSON:
|
|
case DebuggerDump:
|
|
info.is_vector =
|
|
(m_objClass.empty() || m_objCode == 'V' || m_objCode == 'K') &&
|
|
isVectorData;
|
|
if (info.is_vector && m_type == JSON) {
|
|
info.is_vector = (m_option & k_JSON_FORCE_OBJECT)
|
|
? false : info.is_vector;
|
|
}
|
|
|
|
if (info.is_vector) {
|
|
m_buf->append('[');
|
|
} else {
|
|
m_buf->append('{');
|
|
}
|
|
|
|
if (m_type == JSON && m_option & k_JSON_PRETTY_PRINT) {
|
|
m_indent += (info.indent_delta = 4);
|
|
}
|
|
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
|
|
// ...so we don't mess up next array output
|
|
if (!m_objClass.empty() || !m_rsrcName.empty()) {
|
|
m_objClass.clear();
|
|
info.is_object = true;
|
|
} else {
|
|
info.is_object = false;
|
|
}
|
|
}
|
|
|
|
void VariableSerializer::writePropertyKey(CStrRef prop) {
|
|
const char *key = prop.data();
|
|
int kl = prop.size();
|
|
if (!*key && kl) {
|
|
const char *cls = key + 1;
|
|
if (*cls == '*') {
|
|
assert(key[2] == 0);
|
|
m_buf->append(key + 3, kl - 3);
|
|
const char prot[] = "\":protected";
|
|
int o = m_type == PrintR ? 1 : 0;
|
|
m_buf->append(prot + o, sizeof(prot) - 1 - o);
|
|
} else {
|
|
int l = strlen(cls);
|
|
m_buf->append(cls + l + 1, kl - l - 2);
|
|
int o = m_type == PrintR ? 1 : 0;
|
|
m_buf->append("\":\"" + o, 3 - 2*o);
|
|
m_buf->append(cls, l);
|
|
const char priv[] = "\":private";
|
|
m_buf->append(priv + o, sizeof(priv) - 1 - o);
|
|
}
|
|
} else {
|
|
m_buf->append(prop);
|
|
if (m_type != PrintR) m_buf->append('"');
|
|
}
|
|
}
|
|
|
|
/* key MUST be a non-reference string or int */
|
|
void VariableSerializer::writeArrayKey(Variant key) {
|
|
Variant::TypedValueAccessor tva = key.getTypedAccessor();
|
|
bool skey = Variant::IsString(tva);
|
|
if (skey && m_type == APCSerialize) {
|
|
write(Variant::GetAsString(tva));
|
|
return;
|
|
}
|
|
ArrayInfo &info = m_arrayInfos.back();
|
|
switch (m_type) {
|
|
case PrintR: {
|
|
indent();
|
|
m_buf->append('[');
|
|
if (info.is_object && skey) {
|
|
writePropertyKey(Variant::GetAsString(tva));
|
|
} else {
|
|
m_buf->append(key);
|
|
}
|
|
m_buf->append("] => ");
|
|
break;
|
|
}
|
|
case VarExport:
|
|
case PHPOutput:
|
|
indent();
|
|
write(key, true);
|
|
m_buf->append(" => ");
|
|
break;
|
|
case VarDump:
|
|
case DebugDump:
|
|
indent();
|
|
m_buf->append('[');
|
|
if (!skey) {
|
|
m_buf->append(Variant::GetInt64(tva));
|
|
} else {
|
|
m_buf->append('"');
|
|
if (info.is_object) {
|
|
writePropertyKey(Variant::GetAsString(tva));
|
|
} else {
|
|
m_buf->append(Variant::GetAsString(tva));
|
|
m_buf->append('"');
|
|
}
|
|
}
|
|
m_buf->append("]=>\n");
|
|
break;
|
|
case APCSerialize:
|
|
case Serialize:
|
|
case DebuggerSerialize:
|
|
write(key);
|
|
break;
|
|
case JSON:
|
|
case DebuggerDump:
|
|
if (!info.first_element) {
|
|
m_buf->append(',');
|
|
}
|
|
if (m_type == JSON && m_option & k_JSON_PRETTY_PRINT) {
|
|
m_buf->append("\n");
|
|
indent();
|
|
}
|
|
if (!info.is_vector) {
|
|
if (skey) {
|
|
CStrRef s = Variant::GetAsString(tva);
|
|
const char *k = s.data();
|
|
int len = s.size();
|
|
if (info.is_object && !*k && len) {
|
|
while (*++k) len--;
|
|
k++;
|
|
len -= 2;
|
|
}
|
|
write(k, len);
|
|
} else {
|
|
m_buf->append('"');
|
|
m_buf->append(Variant::GetInt64(tva));
|
|
m_buf->append('"');
|
|
}
|
|
m_buf->append(':');
|
|
if (m_type == JSON && m_option & k_JSON_PRETTY_PRINT) {
|
|
m_buf->append(' ');
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void VariableSerializer::writeCollectionKey(CVarRef key) {
|
|
if (m_type == Serialize || m_type == APCSerialize ||
|
|
m_type == DebuggerSerialize) {
|
|
m_valueCount++;
|
|
}
|
|
writeArrayKey(key);
|
|
}
|
|
|
|
void VariableSerializer::writeCollectionKeylessPrefix() {
|
|
ArrayInfo &info = m_arrayInfos.back();
|
|
switch (m_type) {
|
|
case PrintR:
|
|
case VarExport:
|
|
case PHPOutput:
|
|
indent();
|
|
break;
|
|
case VarDump:
|
|
case DebugDump:
|
|
case APCSerialize:
|
|
case Serialize:
|
|
case DebuggerSerialize:
|
|
break;
|
|
case JSON:
|
|
case DebuggerDump:
|
|
if (!info.first_element) {
|
|
m_buf->append(',');
|
|
}
|
|
if (m_type == JSON && m_option & k_JSON_PRETTY_PRINT) {
|
|
m_buf->append("\n");
|
|
indent();
|
|
}
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void VariableSerializer::writeArrayValue(CVarRef value) {
|
|
// Do not count referenced values after the first
|
|
if ((m_type == Serialize || m_type == APCSerialize ||
|
|
m_type == DebuggerSerialize) &&
|
|
!(value.isReferenced() &&
|
|
m_arrayIds->find(value.getRefData()) != m_arrayIds->end())) {
|
|
m_valueCount++;
|
|
}
|
|
|
|
write(value);
|
|
switch (m_type) {
|
|
case PrintR:
|
|
m_buf->append('\n');
|
|
break;
|
|
case VarExport:
|
|
case PHPOutput:
|
|
m_buf->append(",\n");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ArrayInfo &info = m_arrayInfos.back();
|
|
info.first_element = false;
|
|
}
|
|
|
|
void VariableSerializer::writeArrayFooter() {
|
|
ArrayInfo &info = m_arrayInfos.back();
|
|
|
|
m_indent -= info.indent_delta;
|
|
switch (m_type) {
|
|
case PrintR:
|
|
if (m_rsrcName.empty()) {
|
|
indent();
|
|
m_buf->append(")\n");
|
|
if (m_indent > 0) {
|
|
m_indent -= 4;
|
|
}
|
|
}
|
|
break;
|
|
case VarExport:
|
|
case PHPOutput:
|
|
indent();
|
|
if (info.is_object) {
|
|
m_buf->append("))");
|
|
} else {
|
|
m_buf->append(')');
|
|
}
|
|
break;
|
|
case VarDump:
|
|
case DebugDump:
|
|
if (m_rsrcName.empty()) {
|
|
indent();
|
|
m_buf->append("}\n");
|
|
}
|
|
break;
|
|
case Serialize:
|
|
case APCSerialize:
|
|
case DebuggerSerialize:
|
|
m_buf->append('}');
|
|
break;
|
|
case JSON:
|
|
case DebuggerDump:
|
|
if (m_type == JSON && m_option & k_JSON_PRETTY_PRINT) {
|
|
m_buf->append("\n");
|
|
indent();
|
|
}
|
|
if (info.is_vector) {
|
|
m_buf->append(']');
|
|
} else {
|
|
m_buf->append('}');
|
|
}
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
|
|
m_arrayInfos.pop_back();
|
|
}
|
|
|
|
void VariableSerializer::writeSerializableObject(CStrRef clsname,
|
|
CStrRef serialized) {
|
|
m_buf->append("C:");
|
|
m_buf->append(clsname.size());
|
|
m_buf->append(":\"");
|
|
m_buf->append(clsname.data(), clsname.size());
|
|
m_buf->append("\":");
|
|
m_buf->append(serialized.size());
|
|
m_buf->append(":{");
|
|
m_buf->append(serialized.data(), serialized.size());
|
|
m_buf->append('}');
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void VariableSerializer::indent() {
|
|
for (int i = 0; i < m_indent; i++) {
|
|
m_buf->append(' ');
|
|
}
|
|
if (m_referenced) {
|
|
if (m_indent > 0 && m_type == VarDump) m_buf->append('&');
|
|
m_referenced = false;
|
|
}
|
|
}
|
|
|
|
bool VariableSerializer::incNestedLevel(void *ptr,
|
|
bool isObject /* = false */) {
|
|
switch (m_type) {
|
|
case VarExport:
|
|
case PHPOutput:
|
|
case PrintR:
|
|
case VarDump:
|
|
case DebugDump:
|
|
case JSON:
|
|
case DebuggerDump:
|
|
return ++m_counts[ptr] >= m_maxCount;
|
|
case DebuggerSerialize:
|
|
if (m_maxLevelDebugger > 0 && ++m_levelDebugger > m_maxLevelDebugger) {
|
|
return true;
|
|
}
|
|
// fall through
|
|
case Serialize:
|
|
case APCSerialize:
|
|
{
|
|
assert(m_arrayIds);
|
|
int ct = ++m_counts[ptr];
|
|
if (m_arrayIds->find(ptr) != m_arrayIds->end() &&
|
|
(m_referenced || isObject)) {
|
|
return true;
|
|
} else {
|
|
(*m_arrayIds)[ptr] = m_valueCount;
|
|
}
|
|
return ct >= (m_maxCount - 1);
|
|
}
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void VariableSerializer::decNestedLevel(void *ptr) {
|
|
--m_counts[ptr];
|
|
if (m_type == DebuggerSerialize && m_maxLevelDebugger > 0) {
|
|
--m_levelDebugger;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|