hphp/runtime/ext/thrift/compact.cpp (1,057 lines of code) (raw):
/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
| Copyright (c) 1997-2010 The PHP Group |
+----------------------------------------------------------------------+
| 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 "hphp/runtime/ext/thrift/ext_thrift.h"
#include "hphp/runtime/base/array-init.h"
#include "hphp/runtime/base/request-event-handler.h"
#include "hphp/runtime/base/runtime-error.h"
#include "hphp/runtime/base/runtime-option.h"
#include "hphp/runtime/base/strings.h"
#include "hphp/runtime/ext/collections/ext_collections-map.h"
#include "hphp/runtime/ext/collections/ext_collections-set.h"
#include "hphp/runtime/ext/collections/ext_collections-vector.h"
#include "hphp/runtime/ext/reflection/ext_reflection.h"
#include "hphp/runtime/ext/thrift/adapter.h"
#include "hphp/runtime/ext/thrift/field_wrapper.h"
#include "hphp/runtime/ext/thrift/spec-holder.h"
#include "hphp/runtime/ext/thrift/transport.h"
#include "hphp/runtime/ext/thrift/util.h"
#include "hphp/runtime/vm/vm-regs.h"
#include "hphp/runtime/vm/coeffects.h"
#include "hphp/runtime/vm/jit/perf-counters.h"
#include "hphp/util/fixed-vector.h"
#include "hphp/util/rds-local.h"
#include <folly/AtomicHashMap.h>
#include <folly/Format.h>
#include <limits>
#include <stack>
#include <type_traits>
#include <utility>
namespace HPHP::thrift {
/////////////////////////////////////////////////////////////////////////////
namespace {
const StaticString SKIP_CHECKS_ATTR("ThriftDeprecatedSkipSerializerChecks");
}
const uint8_t VERSION_MASK = 0x1f;
const uint8_t VERSION = 2;
const uint8_t VERSION_LOW = 1;
const uint8_t VERSION_DOUBLE_BE = 2;
const uint8_t PROTOCOL_ID = 0x82;
const uint8_t TYPE_MASK = 0xe0;
const uint8_t TYPE_SHIFT_AMOUNT = 5;
enum CState {
STATE_CLEAR,
STATE_FIELD_WRITE,
STATE_VALUE_WRITE,
STATE_CONTAINER_WRITE,
STATE_BOOL_WRITE,
STATE_FIELD_READ,
STATE_CONTAINER_READ,
STATE_VALUE_READ,
STATE_BOOL_READ
};
enum CType {
C_STOP = 0x00,
C_TRUE = 0x01,
C_FALSE = 0x02,
C_BYTE = 0x03,
C_I16 = 0x04,
C_I32 = 0x05,
C_I64 = 0x06,
C_DOUBLE = 0x07,
C_BINARY = 0x08,
C_LIST = 0x09,
C_SET = 0x0A,
C_MAP = 0x0B,
C_STRUCT = 0x0C,
C_FLOAT = 0x0D
};
enum CListType {
C_LIST_LIST,
C_LIST_SET
};
enum TResponseType {
T_REPLY = 2,
T_EXCEPTION = 3
};
static CType ttype_to_ctype(TType x) {
switch (x) {
case T_STOP:
return C_STOP;
case T_BOOL:
return C_TRUE;
case T_BYTE:
return C_BYTE;
case T_I16:
return C_I16;
case T_I32:
return C_I32;
case T_I64:
return C_I64;
case T_DOUBLE:
return C_DOUBLE;
case T_STRING:
return C_BINARY;
case T_STRUCT:
return C_STRUCT;
case T_LIST:
return C_LIST;
case T_SET:
return C_SET;
case T_MAP:
return C_MAP;
case T_FLOAT:
return C_FLOAT;
default:
thrift_error(
folly::to<std::string>(
"Unknown Thrift data type ", static_cast<int>(x)),
ERR_INVALID_DATA);
}
}
static const TType s_ctype_to_ttype_map[14] {
T_STOP,
T_BOOL,
T_BOOL,
T_BYTE,
T_I16,
T_I32,
T_I64,
T_DOUBLE,
T_STRING,
T_LIST,
T_SET,
T_MAP,
T_STRUCT,
T_FLOAT
};
static TType ctype_to_ttype(CType x) {
uint8_t index = static_cast<uint8_t>(x);
if (UNLIKELY(index > C_FLOAT)) {
thrift_error(
folly::to<std::string>(
"Unknown Compact data type ", static_cast<int>(x)),
ERR_INVALID_DATA);
}
return s_ctype_to_ttype_map[index];
}
struct CompactRequestData final : RequestEventHandler {
CompactRequestData() : version(VERSION) { }
void clear() { version = VERSION; }
void requestInit() override {
clear();
}
void requestShutdown() override {
clear();
}
uint8_t version;
};
IMPLEMENT_STATIC_REQUEST_LOCAL(CompactRequestData, s_compact_request_data);
namespace {
struct FieldInfo {
Class* cls = nullptr;
// A pointer to a property which may be set lazily by calling
// cls->lookupDeclProp(fieldName) first time we need the property.
mutable const Class::Prop *prop = nullptr;
const StringData* fieldName = nullptr;
int16_t fieldNum = 0;
const Class::Prop* getProp() const {
if (!prop) {
auto slot = cls->lookupDeclProp(fieldName);
if (slot != kInvalidSlot) {
prop = &cls->declProperties()[slot];
}
}
return prop;
}
};
}
struct CompactWriter {
explicit CompactWriter(PHPOutputTransport *transport) :
transport(transport),
version(VERSION),
state(STATE_CLEAR),
lastFieldNum(0),
boolFieldNum(0),
structHistory(),
containerHistory() {
}
void setWriteVersion(uint8_t _version) {
version = _version;
}
void writeHeader(const String& name, uint8_t msgtype, uint32_t seqid) {
writeUByte(PROTOCOL_ID);
writeUByte(version | (msgtype << TYPE_SHIFT_AMOUNT));
writeVarint(seqid);
writeString(name);
state = STATE_VALUE_WRITE;
}
void write(const Object& obj) {
writeStruct(obj);
}
private:
PHPOutputTransport* transport;
uint8_t version;
CState state;
uint16_t lastFieldNum;
uint16_t boolFieldNum;
std::stack<std::pair<CState, uint16_t> > structHistory;
std::stack<CState> containerHistory;
void writeSlow(const FieldSpec& field, const Object& obj) {
INC_TPC(thrift_write_slow);
StrNR fieldName(field.name);
Variant fieldVal;
if (field.isWrapped) {
fieldVal = getThriftType(obj, fieldName);
} else {
fieldVal = obj->o_get(fieldName, true, obj->getClassName());
}
if (!fieldVal.isNull()) {
TType fieldType = field.type;
writeFieldBegin(field.fieldNum, fieldType);
auto fieldInfo = FieldInfo();
fieldInfo.cls = obj->getVMClass();
fieldInfo.fieldName = field.name;
fieldInfo.fieldNum = field.fieldNum;
writeField(fieldVal, field, fieldType, fieldInfo);
writeFieldEnd();
}
}
void writeStruct(const Object& obj) {
// Save state
structHistory.push(std::make_pair(state, lastFieldNum));
state = STATE_FIELD_WRITE;
lastFieldNum = 0;
// Get field specification
Class* cls = obj->getVMClass();
SpecHolder specHolder;
auto const& fields = specHolder.getSpec(cls).fields;
auto prop = cls->declProperties().begin();
auto objProps = obj->props();
const size_t numProps = cls->numDeclProperties();
const size_t numFields = fields.size();
// Write each member
for (int slot = 0; slot < numFields; ++slot) {
if (slot < numProps && fields[slot].name == prop[slot].name) {
auto index = cls->propSlotToIndex(slot);
VarNR fieldWrapper(objProps->at(index).tv());
const Variant& fieldVal = fieldWrapper;
if (!fieldVal.isNull()) {
TType fieldType = fields[slot].type;
writeFieldBegin(fields[slot].fieldNum, fieldType);
auto fieldInfo = FieldInfo();
fieldInfo.cls = cls;
fieldInfo.prop = &prop[slot];
fieldInfo.fieldNum = fields[slot].fieldNum;
if (fields[slot].isWrapped) {
auto value = getThriftType(obj, StrNR(fields[slot].name));
writeField(value, fields[slot], fieldType, fieldInfo);
} else {
writeField(fieldVal, fields[slot], fieldType, fieldInfo);
}
writeFieldEnd();
} else if (UNLIKELY(fieldVal.is(KindOfUninit)) &&
(prop[slot].attrs & AttrLateInit)) {
throw_late_init_prop(prop[slot].cls, prop[slot].name, false);
}
} else {
writeSlow(fields[slot], obj);
}
}
// Write stop
writeUByte(0);
// Restore state
std::pair<CState, uint16_t> prev = structHistory.top();
state = prev.first;
lastFieldNum = prev.second;
structHistory.pop();
}
void writeFieldBegin(uint16_t fieldNum, TType fieldType) {
if (fieldType == T_BOOL) {
state = STATE_BOOL_WRITE;
boolFieldNum = fieldNum;
// the value and header are written together in writeField
} else {
state = STATE_VALUE_WRITE;
writeFieldHeader(fieldNum, ttype_to_ctype(fieldType));
}
}
void writeFieldEnd(void) {
state = STATE_FIELD_WRITE;
}
void writeFieldHeader(uint16_t fieldNum, CType fieldType) {
int delta = fieldNum - lastFieldNum;
if (0 < delta && delta <= 15) {
writeUByte((delta << 4) | fieldType);
} else {
writeUByte(fieldType);
writeI(fieldNum);
}
lastFieldNum = fieldNum;
}
void writeField(const Variant& value,
const FieldSpec& valueSpec,
TType type,
const FieldInfo& fieldInfo) {
const auto& thriftValue = valueSpec.adapter ? transformToThriftType(
value, *valueSpec.adapter)
: value;
writeFieldInternal(thriftValue, valueSpec, type, fieldInfo);
}
void writeFieldInternal(const Variant& value,
const FieldSpec& valueSpec,
TType type,
const FieldInfo& fieldInfo) {
switch (type) {
case T_STOP:
case T_VOID:
break;
case T_STRUCT:
if (!value.is(KindOfObject)) {
thrift_error("Attempt to send non-object type as T_STRUCT",
ERR_INVALID_DATA);
}
writeStruct(value.toObject());
break;
case T_BOOL: {
bool b = value.toBoolean();
if (state == STATE_BOOL_WRITE) {
CType t = b ? C_TRUE : C_FALSE;
writeFieldHeader(boolFieldNum, t);
} else if (state == STATE_CONTAINER_WRITE) {
writeUByte(b ? C_TRUE : C_FALSE);
} else {
thrift_error("Invalid state in compact protocol", ERR_UNKNOWN);
}
}
break;
case T_BYTE:
writeUByte(value.toByte());
break;
case T_I16:
writeIChecked<std::int16_t>(value, fieldInfo);
break;
case T_I32:
writeIChecked<std::int32_t>(value, fieldInfo);
break;
case T_I64:
case T_U64:
writeI(value.toInt64());
break;
case T_DOUBLE: {
union {
uint64_t i;
double d;
} u;
u.d = value.toDouble();
uint64_t bits;
if (version >= VERSION_DOUBLE_BE) {
bits = htonll(u.i);
} else {
bits = htolell(u.i);
}
transport->write((char*)&bits, 8);
}
break;
case T_FLOAT: {
union {
uint32_t i;
float d;
} u;
u.d = (float)value.toDouble();
uint32_t bits = htonl(u.i);
transport->write((char*)&bits, 4);
}
break;
case T_UTF8:
case T_UTF16:
case T_STRING: {
if (value.is(KindOfObject)) {
thrift_error("Attempt to send object type as a T_STRING",
ERR_INVALID_DATA);
}
auto s = value.toString();
auto slice = s.slice();
writeVarint(slice.size());
transport->write(slice.data(), slice.size());
break;
}
case T_MAP:
writeMap(value, valueSpec, fieldInfo);
break;
case T_LIST:
writeList(value, valueSpec, C_LIST_LIST, fieldInfo);
break;
case T_SET:
writeList(value, valueSpec, C_LIST_SET, fieldInfo);
break;
default:
thrift_error("Unknown Thrift data type",
ERR_INVALID_DATA);
}
}
void writeMap(const Variant& map,
const FieldSpec& spec,
const FieldInfo& fieldInfo) {
auto elemWriter = [&](TypedValue k, TypedValue v) {
writeField(VarNR(k), spec.key(), spec.ktype, fieldInfo);
writeField(VarNR(v), spec.val(), spec.vtype, fieldInfo);
return false;
};
if (isContainer(map)) {
writeMapBegin(spec.ktype, spec.vtype, getContainerSize(map));
IterateKV(*map.asTypedValue(), elemWriter);
} else {
auto const arr = map.toArray();
writeMapBegin(spec.ktype, spec.vtype, arr.size());
IterateKV(arr.get(), elemWriter);
}
writeCollectionEnd();
}
void writeList(const Variant& list,
const FieldSpec& spec,
CListType listType,
const FieldInfo& fieldInfo) {
auto const listWriter = [&](TypedValue v) {
writeField(VarNR(v), spec.val(), spec.vtype, fieldInfo);
};
auto const setWriter = [&](TypedValue k, TypedValue /*v*/) {
writeField(VarNR(k), spec.val(), spec.vtype, fieldInfo);
};
always_assert(listType == C_LIST_LIST ||
listType == C_LIST_SET);
if (isContainer(list)) {
writeListBegin(spec.vtype, getContainerSize(list));
if (listType == C_LIST_LIST) {
IterateV(*list.asTypedValue(), listWriter);
} else {
IterateKV(*list.asTypedValue(), setWriter);
}
} else {
auto const arr = list.toArray();
writeListBegin(spec.vtype, arr.size());
if (listType == C_LIST_LIST) {
IterateV(arr.get(), listWriter);
} else {
IterateKV(arr.get(), setWriter);
}
}
writeCollectionEnd();
}
void writeMapBegin(TType keyType, TType valueType, uint32_t size) {
if (size == 0) {
writeUByte(0);
} else {
writeVarint(size);
CType keyCType = ttype_to_ctype(keyType);
CType valueCType = ttype_to_ctype(valueType);
writeUByte((keyCType << 4) | valueCType);
}
containerHistory.push(state);
state = STATE_CONTAINER_WRITE;
}
void writeListBegin(TType elemType, uint32_t size) {
if (size <= 14) {
writeUByte((size << 4) | ttype_to_ctype(elemType));
} else {
writeUByte(0xf0 | ttype_to_ctype(elemType));
writeVarint(size);
}
containerHistory.push(state);
state = STATE_CONTAINER_WRITE;
}
void writeCollectionEnd(void) {
state = containerHistory.top();
containerHistory.pop();
}
void writeUByte(uint8_t n) {
transport->writeI8(n);
}
void writeI(int64_t n) {
writeVarint(i64ToZigzag(n));
}
template <typename T>
void writeIChecked(const Variant& value, const FieldInfo& fieldInfo) {
static_assert(std::is_integral<T>::value, "not an integral type");
auto n = value.toInt64();
using limits = std::numeric_limits<T>;
if ((n < limits::min() || n > limits::max())) {
const auto& structName = fieldInfo.cls->nameStr();
std::string message = folly::sformat(
"Value {} is out of range in field {} of {}",
n,
fieldInfo.fieldNum,
structName.c_str());
auto hasSkipChecksAttr = [&]() {
const Class::Prop* prop = fieldInfo.getProp();
return prop &&
prop->preProp->userAttributes().count(
LowStringPtr(SKIP_CHECKS_ATTR.get())) != 0;
};
if (hasSkipChecksAttr()) {
raise_warning("[Suppressed] " + message);
} else {
thrift_error(message, ERR_INVALID_DATA);
}
}
writeI(n);
}
void writeVarint(uint64_t n) {
uint8_t buf[10];
uint8_t wsize = 0;
while (true) {
if ((n & ~0x7FL) == 0) {
buf[wsize++] = (int8_t)n;
break;
} else {
buf[wsize++] = (int8_t)((n & 0x7F) | 0x80);
n >>= 7;
}
}
transport->write((char*)buf, wsize);
}
void writeString(const String& s) {
auto slice = s.slice();
writeVarint(slice.size());
transport->write(slice.data(), slice.size());
}
uint64_t i64ToZigzag(int64_t n) {
return (static_cast<uint64_t>(n) << 1) ^ (n >> 63);
}
};
struct CompactReader {
explicit CompactReader(const Object& _transportobj, int options) :
transport(_transportobj),
options(options),
version(VERSION),
state(STATE_CLEAR),
lastFieldNum(0),
boolValue(true),
structHistory(),
containerHistory() {
}
Variant read(const String& resultClassName) {
uint8_t protoId = readUByte();
if (protoId != PROTOCOL_ID) {
thrift_error("Bad protocol id in TCompact message", ERR_BAD_VERSION);
}
uint8_t versionAndType = readUByte();
uint8_t type = (versionAndType & TYPE_MASK) >> TYPE_SHIFT_AMOUNT;
version = versionAndType & VERSION_MASK;
if (version < VERSION_LOW || version > VERSION) {
thrift_error("Bad version in TCompact message", ERR_BAD_VERSION);
}
// TODO: we eventually want to return seqid to the caller
readVarint(); // seqid (unused)
skip(T_STRING); // name (unused)
if (type == T_REPLY) {
return readStruct(resultClassName);
} else if (type == T_EXCEPTION) {
throw_object(readStruct(s_TApplicationException));
} else {
thrift_error("Invalid response type", ERR_INVALID_DATA);
}
}
NEVER_INLINE
void readStructSlow(const Object& dest,
const StructSpec& spec,
int16_t fieldNum,
TType fieldType) {
INC_TPC(thrift_read_slow);
while (fieldType != T_STOP) {
bool readComplete = false;
const auto* fieldSpec = getFieldSlow(spec, fieldNum);
if (fieldSpec) {
if (typesAreCompatible(fieldType, fieldSpec->type)) {
readComplete = true;
Variant fieldValue = readField(*fieldSpec, fieldType);
if (fieldSpec->isWrapped) {
setThriftType(fieldValue, dest, StrNR(fieldSpec->name));
} else {
dest->o_set(
StrNR(fieldSpec->name), fieldValue, dest->getClassName());
}
if (fieldSpec->isUnion) {
dest->o_set(s__type, Variant(fieldNum), dest->getClassName());
}
}
}
if (!readComplete) {
INC_TPC(thrift_spec_slow);
skip(fieldType);
}
readFieldEnd();
readFieldBegin(fieldNum, fieldType);
}
readStructEnd();
assertx(dest->assertPropTypeHints());
}
Object readStruct(const String& clsName) {
auto const cls = Class::load(clsName.get());
if (cls == nullptr) raise_error(Strings::UNKNOWN_CLASS, clsName.data());
SpecHolder specHolder;
auto const& spec = specHolder.getSpec(cls);
Object dest = spec.newObject(cls);
readStructBegin();
int16_t fieldNum;
TType fieldType;
readFieldBegin(fieldNum, fieldType);
auto const& fields = spec.fields;
const size_t numFields = fields.size();
if (cls->numDeclProperties() < numFields) {
readStructSlow(dest, spec, fieldNum, fieldType);
return dest;
}
auto objProp = dest->props();
auto prop = cls->declProperties().begin();
int slot = -1;
while (fieldType != T_STOP) {
do {
++slot;
} while (slot < numFields && fields[slot].fieldNum != fieldNum);
if (slot == numFields ||
prop[slot].name != fields[slot].name ||
!typesAreCompatible(fieldType, fields[slot].type)) {
readStructSlow(dest, spec, fieldNum, fieldType);
return dest;
}
if (fields[slot].isUnion) {
if (s__type.equal(prop[numFields].name)) {
auto index = cls->propSlotToIndex(numFields);
tvSetInt(fieldNum, objProp->at(index));
} else {
readStructSlow(dest, spec, fieldNum, fieldType);
return dest;
}
}
auto index = cls->propSlotToIndex(slot);
auto value = readField(fields[slot], fieldType);
if (fields[slot].isWrapped) {
setThriftType(value, dest, StrNR(fields[slot].name));
} else {
tvSet(*value.asTypedValue(), objProp->at(index));
}
if (!fields[slot].noTypeCheck) {
dest->verifyPropTypeHint(slot);
if (fields[slot].isUnion) dest->verifyPropTypeHint(numFields);
}
readFieldEnd();
readFieldBegin(fieldNum, fieldType);
}
readStructEnd();
assertx(dest->assertPropTypeHints());
return dest;
}
private:
PHPInputTransport transport;
int options;
uint8_t version;
CState state;
uint16_t lastFieldNum;
bool boolValue;
std::stack<std::pair<CState, uint16_t> > structHistory;
std::stack<CState> containerHistory;
void readStructBegin(void) {
structHistory.push(std::make_pair(state, lastFieldNum));
state = STATE_FIELD_READ;
lastFieldNum = 0;
}
void readStructEnd(void) {
std::pair<CState, uint16_t> prev = structHistory.top();
state = prev.first;
lastFieldNum = prev.second;
structHistory.pop();
}
void readFieldBegin(int16_t &fieldNum, TType &fieldType) {
uint8_t fieldTypeAndDelta = readUByte();
int delta = fieldTypeAndDelta >> 4;
CType fieldCType = (CType)(fieldTypeAndDelta & 0x0f);
fieldType = ctype_to_ttype(fieldCType);
if (fieldCType == C_STOP) {
fieldNum = 0;
return;
}
if (delta == 0) {
fieldNum = readI();
} else {
fieldNum = lastFieldNum + delta;
}
lastFieldNum = fieldNum;
if (fieldCType == C_TRUE) {
state = STATE_BOOL_READ;
boolValue = true;
} else if (fieldCType == C_FALSE) {
state = STATE_BOOL_READ;
boolValue = false;
} else {
state = STATE_VALUE_READ;
}
}
void readFieldEnd(void) {
state = STATE_FIELD_READ;
}
Variant readField(const FieldSpec& spec, TType type) {
const auto thriftValue = readFieldInternal(spec, type);
return spec.adapter ? transformToHackType(thriftValue, *spec.adapter)
: thriftValue;
}
Variant readFieldInternal(const FieldSpec& spec, TType type) {
switch (type) {
case T_STOP:
case T_VOID:
return init_null();
case T_STRUCT:
return readStruct(spec.className());
case T_BOOL:
if (state == STATE_BOOL_READ) {
return boolValue;
} else if (state == STATE_CONTAINER_READ) {
return (readUByte() == C_TRUE);
} else {
thrift_error("Invalid state in compact protocol", ERR_UNKNOWN);
}
case T_BYTE:
return transport.readI8();
case T_I16:
case T_I32:
case T_I64:
case T_U64:
return readI();
case T_DOUBLE: {
union {
uint64_t i;
double d;
} u;
transport.readBytes(&(u.i), 8);
if (version >= VERSION_DOUBLE_BE) {
u.i = ntohll(u.i);
} else {
u.i = letohll(u.i);
}
return u.d;
}
case T_FLOAT: {
union {
uint32_t i;
float d;
} u;
transport.readBytes(&(u.i), 4);
u.i = ntohl(u.i);
return u.d;
}
case T_UTF8:
case T_UTF16:
case T_STRING:
return readString();
case T_MAP:
return readMap(spec);
case T_LIST:
return readList(spec);
case T_SET:
return readSet(spec);
default:
thrift_error("Unknown Thrift data type",
ERR_INVALID_DATA);
}
}
void skip(TType type) {
switch (type) {
case T_STOP:
case T_VOID:
thrift_error("Encountered invalid type for skipping T_STOP/T_VOID",
ERR_INVALID_DATA);
break;
case T_STRUCT: {
readStructBegin();
while (true) {
int16_t fieldNum;
TType fieldType;
readFieldBegin(fieldNum, fieldType);
if (fieldType == T_STOP) {
break;
}
skip(fieldType);
readFieldEnd();
}
readStructEnd();
}
break;
case T_BOOL:
if (state == STATE_BOOL_READ) {
// don't need to do anything
} else if (state == STATE_CONTAINER_READ) {
readUByte();
} else {
thrift_error("Invalid state in compact protocol", ERR_UNKNOWN);
}
break;
case T_BYTE:
readUByte();
break;
case T_I16:
case T_I32:
case T_I64:
case T_U64:
readI();
break;
case T_DOUBLE:
transport.skip(8);
break;
case T_FLOAT:
transport.skip(4);
break;
case T_UTF8:
case T_UTF16:
case T_STRING:
transport.skip(readVarint());
break;
case T_MAP: {
TType keyType, valueType;
uint32_t size;
readMapBegin(keyType, valueType, size);
for (uint32_t i = 0; i < size; i++) {
skip(keyType);
skip(valueType);
}
readCollectionEnd();
}
break;
case T_LIST:
case T_SET: {
TType valueType;
uint32_t size;
readListBegin(valueType, size);
for (uint32_t i = 0; i < size; i++) {
skip(valueType);
}
readCollectionEnd();
}
break;
default:
thrift_error("Unknown Thrift data type",
ERR_INVALID_DATA);
}
}
Variant readMap(const FieldSpec& spec) {
TType keyType, valueType;
uint32_t size;
readMapBegin(keyType, valueType, size);
if (s_harray.equal(spec.format)) {
DictInit arr(size);
for (uint32_t i = 0; i < size; i++) {
switch (keyType) {
case TType::T_I08:
case TType::T_I16:
case TType::T_I32:
case TType::T_I64: {
int64_t key = readField(spec.key(), keyType).toInt64();
Variant value = readField(spec.val(), valueType);
arr.set(key, value);
break;
}
case TType::T_STRING: {
String key = readField(spec.key(), keyType).toString();
Variant value = readField(spec.val(), valueType);
arr.set(key, value);
break;
}
default:
thrift_error(
"Unable to deserialize non int/string array keys",
ERR_INVALID_DATA);
}
}
readCollectionEnd();
return arr.toVariant();
} else if (s_collection.equal(spec.format)) {
auto ret(req::make<c_Map>(size));
for (uint32_t i = 0; i < size; i++) {
Variant key = readField(spec.key(), keyType);
Variant value = readField(spec.val(), valueType);
BaseMap::OffsetSet(ret.get(), key.asTypedValue(), value.asTypedValue());
}
readCollectionEnd();
return Variant(std::move(ret));
} else {
DictInit arr(size);
if (options & k_THRIFT_MARK_LEGACY_ARRAYS) {
arr.setLegacyArray();
}
for (uint32_t i = 0; i < size; i++) {
auto key = readField(spec.key(), keyType);
auto value = readField(spec.val(), valueType);
set_with_intish_key_cast(arr, key, value);
}
readCollectionEnd();
return arr.toVariant();
}
}
Variant readList(const FieldSpec& spec) {
TType valueType;
uint32_t size;
readListBegin(valueType, size);
if (s_harray.equal(spec.format)) {
VecInit arr(size);
for (uint32_t i = 0; i < size; i++) {
arr.append(readField(spec.val(), valueType));
}
readCollectionEnd();
return arr.toVariant();
} else if (s_collection.equal(spec.format)) {
if (size == 0) {
readCollectionEnd();
return Variant(req::make<c_Vector>());
}
auto vec = req::make<c_Vector>(size);
int64_t i = 0;
do {
auto val = readField(spec.val(), valueType);
tvDup(*val.asTypedValue(), vec->appendForUnserialize(i));
} while (++i < size);
readCollectionEnd();
return Variant(std::move(vec));
} else {
VecInit vai(size);
if (options & k_THRIFT_MARK_LEGACY_ARRAYS) {
vai.setLegacyArray(true);
}
for (auto i = uint32_t{0}; i < size; ++i) {
vai.append(readField(spec.val(), valueType));
}
readCollectionEnd();
return vai.toVariant();
}
}
Variant readSet(const FieldSpec& spec) {
TType valueType;
uint32_t size;
readListBegin(valueType, size);
if (s_harray.equal(spec.format)) {
KeysetInit arr(size);
for (uint32_t i = 0; i < size; i++) {
arr.add(readField(spec.val(), valueType));
}
readCollectionEnd();
return arr.toVariant();
} else if (s_collection.equal(spec.format)) {
auto set_ret = req::make<c_Set>(size);
for (uint32_t i = 0; i < size; i++) {
Variant value = readField(spec.val(), valueType);
set_ret->add(value);
}
readCollectionEnd();
return Variant(std::move(set_ret));
} else {
DictInit ainit(size);
if (options & k_THRIFT_MARK_LEGACY_ARRAYS) {
ainit.setLegacyArray();
}
for (uint32_t i = 0; i < size; i++) {
Variant value = readField(spec.val(), valueType);
set_with_intish_key_cast(ainit, value, true);
}
readCollectionEnd();
return ainit.toVariant();
}
}
void readMapBegin(TType &keyType, TType &valueType, uint32_t &size) {
size = readVarint();
uint8_t types = 0;
if (size > 0) {
types = readUByte();
}
valueType = ctype_to_ttype((CType)(types & 0x0f));
keyType = ctype_to_ttype((CType)(types >> 4));
containerHistory.push(state);
state = STATE_CONTAINER_READ;
}
void readListBegin(TType &elemType, uint32_t &size) {
uint8_t sizeAndType = readUByte();
size = sizeAndType >> 4;
elemType = ctype_to_ttype((CType)(sizeAndType & 0x0f));
if (size == 15) {
size = readVarint();
}
containerHistory.push(state);
state = STATE_CONTAINER_READ;
}
void readCollectionEnd(void) {
state = containerHistory.top();
containerHistory.pop();
}
uint8_t readUByte(void) {
return transport.readI8();
}
int64_t readI(void) {
return zigzagToI64(readVarint());
}
uint64_t readVarint(void) {
uint64_t result = 0;
uint8_t shift = 0;
while (true) {
uint8_t byte = readUByte();
result |= (uint64_t)(byte & 0x7f) << shift;
shift += 7;
if (!(byte & 0x80)) {
return result;
}
// Should never read more than 10 bytes, which is the max for a 64-bit
// int
if (shift >= 10 * 7) {
thrift_error("Variable-length int over 10 bytes", ERR_INVALID_DATA);
}
}
}
String readString(void) {
uint32_t size = readVarint();
if (size && (size + 1)) {
String s = String(size, ReserveString);
char* buf = s.mutableData();
transport.readBytes(buf, size);
s.setSize(size);
return s;
} else {
transport.skip(size);
return empty_string();
}
}
int64_t zigzagToI64(uint64_t n) {
return (n >> 1) ^ -(n & 1);
}
bool typeIsInt(TType t) {
return (t == T_BYTE) || ((t >= T_I16) && (t <= T_I64));
}
bool typesAreCompatible(TType t1, TType t2) {
return (t1 == t2) || (typeIsInt(t1) && (typeIsInt(t2)));
}
};
int64_t HHVM_FUNCTION(thrift_protocol_set_compact_version,
int version) {
int result = s_compact_request_data->version;
s_compact_request_data->version = (uint8_t)version;
return result;
}
void HHVM_FUNCTION(thrift_protocol_write_compact,
const Object& transportobj,
const String& method_name,
int64_t msgtype,
const Object& request_struct,
int seqid,
bool oneway) {
CoeffectsAutoGuard _;
// Suppress class-to-string conversion warnings that occur during
// serialization and deserialization.
SuppressClassConversionWarning suppressor;
PHPOutputTransport transport(transportobj);
CompactWriter writer(&transport);
writer.setWriteVersion(s_compact_request_data->version);
writer.writeHeader(method_name, (uint8_t)msgtype, (uint32_t)seqid);
writer.write(request_struct);
if (oneway) {
transport.onewayFlush();
} else {
transport.flush();
}
}
Variant HHVM_FUNCTION(thrift_protocol_read_compact,
const Object& transportobj,
const String& obj_typename,
int options) {
CoeffectsAutoGuard _;
// Suppress class-to-string conversion warnings that occur during
// serialization and deserialization.
SuppressClassConversionWarning suppressor;
VMRegAnchor _2;
CompactReader reader(transportobj, options);
return reader.read(obj_typename);
}
Object HHVM_FUNCTION(thrift_protocol_read_compact_struct,
const Object& transportobj,
const String& obj_typename,
int options) {
// Suppress class-to-string conversion warnings that occur during
// serialization and deserialization.
SuppressClassConversionWarning suppressor;
VMRegAnchor _;
CompactReader reader(transportobj, options);
return reader.readStruct(obj_typename);
}
}