hphp/runtime/ext/thrift/binary.cpp (684 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/base/array-init.h" #include "hphp/runtime/base/collections.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/extension.h" #include "hphp/runtime/ext/reflection/ext_reflection.h" #include "hphp/runtime/ext/std/ext_std_classobj.h" #include "hphp/runtime/ext/thrift/adapter.h" #include "hphp/runtime/ext/thrift/field_wrapper.h" #include "hphp/runtime/ext/thrift/ext_thrift.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/logger.h" #include <folly/portability/Sockets.h> #include <folly/portability/Unistd.h> #include <sys/types.h> #include <stdexcept> namespace HPHP::thrift { ///////////////////////////////////////////////////////////////////////////// const StaticString s_getTransport("getTransport"), s_flush("flush"), s_onewayFlush("onewayFlush"), s_write("write"), s_putBack("putBack"), s_read("read"), s__type("_type"), s_collection("collection"), s_harray("harray"), s_TProtocolException("TProtocolException"), s_TApplicationException("TApplicationException"); /////////////////////////////////////////////////////////////////////////////// const int32_t VERSION_MASK = 0xffff0000; const int32_t VERSION_1 = 0x80010000; const int8_t T_CALL UNUSED = 1; const int8_t T_REPLY UNUSED = 2; const int8_t T_EXCEPTION = 3; // tprotocolexception const int INVALID_DATA = 1; const int BAD_VERSION = 4; Object binary_deserialize_struct(const String& clsName, PHPInputTransport& transport, int options); Variant binary_deserialize(int8_t thrift_typeID, PHPInputTransport& transport, const FieldSpec& fieldspec, int options); void binary_serialize_struct(const Object& zthis, PHPOutputTransport& transport); void binary_serialize(int8_t thrift_typeID, PHPOutputTransport& transport, const Variant& value, const FieldSpec& fieldspec); void skip_element(long thrift_typeID, PHPInputTransport& transport); [[noreturn]] NEVER_INLINE void throw_tprotocolexception(const String& what, long errorcode) { throw_object(s_TProtocolException, make_vec_array(what, errorcode)); } Variant binary_deserialize_internal(int8_t thrift_typeID, PHPInputTransport& transport, const FieldSpec& fieldspec, int options) { switch (thrift_typeID) { case T_STOP: case T_VOID: return init_null(); case T_STRUCT: { return binary_deserialize_struct(fieldspec.className(), transport, options); } case T_BOOL: { uint8_t c; transport.readBytes(&c, 1); return c != 0; } //case T_I08: // same numeric value as T_BYTE case T_BYTE: { uint8_t c; transport.readBytes(&c, 1); return Variant((int8_t)c); } case T_I16: { uint16_t c; transport.readBytes(&c, 2); return Variant((int16_t)ntohs(c)); } case T_I32: { uint32_t c; transport.readBytes(&c, 4); return Variant((int32_t)ntohl(c)); } case T_U64: case T_I64: { uint64_t c; transport.readBytes(&c, 8); return Variant((int64_t)ntohll(c)); } case T_DOUBLE: { union { uint64_t c; double d; } a; transport.readBytes(&(a.c), 8); a.c = ntohll(a.c); return a.d; } case T_FLOAT: { union { uint32_t c; float d; } a; transport.readBytes(&(a.c), 4); a.c = ntohl(a.c); return a.d; } //case T_UTF7: // aliases T_STRING case T_UTF8: case T_UTF16: case T_STRING: { uint32_t size = transport.readU32(); if (size && (size + 1)) { String s = String(size, ReserveString); char* strbuf = s.mutableData(); transport.readBytes(strbuf, size); s.setSize(size); return s; } else { return empty_string_variant(); } } case T_MAP: { // array of key -> value uint8_t types[2]; transport.readBytes(types, 2); uint32_t size = transport.readU32(); auto const& key_spec = fieldspec.key(); auto const& val_spec = fieldspec.val(); if (s_harray.equal(fieldspec.format)) { DictInit arr(size); for (uint32_t i = 0; i < size; i++) { switch (types[0]) { case TType::T_I08: case TType::T_I16: case TType::T_I32: case TType::T_I64: { int64_t key = binary_deserialize( types[0], transport, key_spec, options).toInt64(); Variant value = binary_deserialize( types[1], transport, val_spec, options); arr.set(key, value); break; } case TType::T_STRING: { String key = binary_deserialize( types[0], transport, key_spec, options).toString(); Variant value = binary_deserialize( types[1], transport, val_spec, options); arr.set(key, value); break; } default: thrift_error( "Unable to deserialize non int/string array keys", ERR_INVALID_DATA); } } return arr.toVariant(); } else if (s_collection.equal(fieldspec.format)) { auto obj(req::make<c_Map>(size)); for (uint32_t s = 0; s < size; ++s) { auto key = binary_deserialize( types[0], transport, key_spec, options); auto value = binary_deserialize( types[1], transport, val_spec, options); collections::set(obj.get(), key.asTypedValue(), value.asTypedValue()); } return Variant(std::move(obj)); } else { DictInit arr(size); if (options & k_THRIFT_MARK_LEGACY_ARRAYS) { arr.setLegacyArray(); } for (uint32_t i = 0; i < size; i++) { auto key = binary_deserialize( types[0], transport, key_spec, options); auto val = binary_deserialize( types[1], transport, val_spec, options); set_with_intish_key_cast(arr, key, val); } return arr.toVariant(); } } case T_LIST: { // array with autogenerated numeric keys int8_t type = transport.readI8(); uint32_t size = transport.readU32(); auto const& val_spec = fieldspec.val(); if (s_harray.equal(fieldspec.format)) { VecInit arr(size); for (uint32_t i = 0; i < size; i++) { arr.append(binary_deserialize(type, transport, val_spec, options)); } return arr.toVariant(); } else if (s_collection.equal(fieldspec.format)) { if (size == 0) { return Variant(req::make<c_Vector>()); } auto vec = req::make<c_Vector>(size); int64_t i = 0; do { auto val = binary_deserialize(type, transport, val_spec, options); tvDup(*val.asTypedValue(), vec->appendForUnserialize(i)); } while (++i < size); return Variant(std::move(vec)); } else { VecInit vai(size); if (options & k_THRIFT_MARK_LEGACY_ARRAYS) { vai.setLegacyArray(true); } for (auto s = uint32_t{0}; s < size; ++s) { vai.append(binary_deserialize(type, transport, val_spec, options)); } return vai.toVariant(); } } case T_SET: { // array of key -> TRUE uint8_t type; uint32_t size; transport.readBytes(&type, 1); transport.readBytes(&size, 4); size = ntohl(size); auto const& val_spec = fieldspec.val(); if (s_harray.equal(fieldspec.format)) { KeysetInit arr(size); for (uint32_t i = 0; i < size; i++) { arr.add(binary_deserialize(type, transport, val_spec, options)); } return arr.toVariant(); } else if (s_collection.equal(fieldspec.format)) { auto set_ret(req::make<c_Set>(size)); for (uint32_t s = 0; s < size; ++s) { Variant key = binary_deserialize(type, transport, val_spec, options); if (key.isInteger()) { set_ret->add(key); } else { set_ret->add(key.toString()); } } return Variant(std::move(set_ret)); } else { DictInit init(size); if (options & k_THRIFT_MARK_LEGACY_ARRAYS) { init.setLegacyArray(); } for (uint32_t s = 0; s < size; ++s) { Variant key = binary_deserialize(type, transport, val_spec, options); set_with_intish_key_cast(init, key, true); } return init.toVariant(); } } }; char errbuf[128]; snprintf(errbuf, sizeof(errbuf), "Unknown thrift typeID %d", thrift_typeID); throw_tprotocolexception(String(errbuf, CopyString), INVALID_DATA); return init_null(); } Variant binary_deserialize(int8_t thrift_typeID, PHPInputTransport& transport, const FieldSpec& fieldspec, int options) { const auto thriftValue = binary_deserialize_internal(thrift_typeID, transport, fieldspec, options); return fieldspec.adapter ? transformToHackType(thriftValue, *fieldspec.adapter) : thriftValue; } void skip_element(long thrift_typeID, PHPInputTransport& transport) { switch (thrift_typeID) { case T_STOP: case T_VOID: return; case T_STRUCT: while (true) { int8_t ttype = transport.readI8(); // get field type if (ttype == T_STOP) break; transport.skip(2); // skip field number, I16 skip_element(ttype, transport); // skip field payload } return; case T_BOOL: case T_BYTE: transport.skip(1); return; case T_I16: transport.skip(2); return; case T_I32: case T_FLOAT: transport.skip(4); return; case T_U64: case T_I64: case T_DOUBLE: transport.skip(8); return; //case T_UTF7: // aliases T_STRING case T_UTF8: case T_UTF16: case T_STRING: { uint32_t len = transport.readU32(); transport.skip(len); } return; case T_MAP: { int8_t keytype = transport.readI8(); int8_t valtype = transport.readI8(); uint32_t size = transport.readU32(); for (uint32_t i = 0; i < size; ++i) { skip_element(keytype, transport); skip_element(valtype, transport); } } return; case T_LIST: case T_SET: { int8_t valtype = transport.readI8(); uint32_t size = transport.readU32(); for (uint32_t i = 0; i < size; ++i) { skip_element(valtype, transport); } } return; }; char errbuf[128]; snprintf(errbuf, sizeof(errbuf), "Unknown thrift typeID %ld", thrift_typeID); throw_tprotocolexception(String(errbuf, CopyString), INVALID_DATA); } void binary_serialize_hashtable_key(int8_t keytype, PHPOutputTransport& transport, Variant key, const FieldSpec& fieldspec) { bool keytype_is_numeric = (!((keytype == T_STRING) || (keytype == T_UTF8) || (keytype == T_UTF16))); if (keytype_is_numeric) { key = key.toInt64(); } else { key = key.toString(); } binary_serialize(keytype, transport, key, fieldspec); } inline bool ttype_is_int(int8_t t) { return ((t == T_BYTE) || ((t >= T_I16) && (t <= T_I64))); } inline bool ttypes_are_compatible(int8_t t1, int8_t t2) { // Integer types of different widths are considered compatible; // otherwise the typeID must match. return ((t1 == t2) || (ttype_is_int(t1) && ttype_is_int(t2))); } NEVER_INLINE void binary_deserialize_slow(const Object& zthis, const StructSpec& spec, int16_t fieldno, TType ttype, PHPInputTransport& transport, int options) { INC_TPC(thrift_read_slow); while (ttype != T_STOP) { if (const auto* fieldspec = getFieldSlow(spec, fieldno)) { if (ttypes_are_compatible(ttype, fieldspec->type)) { Variant rv = binary_deserialize(ttype, transport, *fieldspec, options); if (fieldspec->isWrapped) { setThriftType(rv, zthis, StrNR(fieldspec->name)); } else { zthis->o_set(StrNR(fieldspec->name), rv, zthis->getClassName()); } if (fieldspec->isUnion) { zthis->o_set(s__type, Variant(fieldno), zthis->getClassName()); } } else { skip_element(ttype, transport); } } else { skip_element(ttype, transport); } ttype = static_cast<TType>(transport.readI8()); if (ttype == T_STOP) return; fieldno = transport.readI16(); } assertx(zthis->assertPropTypeHints()); } Object binary_deserialize_struct(const String& clsName, PHPInputTransport& transport, int options) { 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); auto const& fields = spec.fields; const size_t numFields = fields.size(); if (cls->numDeclProperties() < numFields) { TType fieldType = static_cast<TType>(transport.readI8()); int16_t fieldNum = transport.readI16(); binary_deserialize_slow( dest, spec, fieldNum, fieldType, transport, options); return dest; } auto objProps = dest->props(); auto prop = cls->declProperties().begin(); int i = -1; TType fieldType = static_cast<TType>(transport.readI8()); int16_t fieldNum; while (fieldType != T_STOP) { fieldNum = transport.readI16(); do { ++i; } while (i < numFields && fields[i].fieldNum != fieldNum); if (i == numFields || prop[i].name != fields[i].name || !ttypes_are_compatible(fieldType, fields[i].type)) { // Verify everything we've set so far binary_deserialize_slow( dest, spec, fieldNum, fieldType, transport, options); return dest; } if (fields[i].isUnion) { if (s__type.equal(prop[numFields].name)) { auto index = cls->propSlotToIndex(numFields); tvSetInt(fieldNum, objProps->at(index)); } else { binary_deserialize_slow( dest, spec, fieldNum, fieldType, transport, options); return dest; } } auto index = cls->propSlotToIndex(i); auto value = binary_deserialize(fieldType, transport, fields[i], options); if (fields[i].isWrapped) { setThriftType(value, dest, StrNR(fields[i].name)); } else { tvSet(*value.asTypedValue(), objProps->at(index)); } if (!fields[i].noTypeCheck) { dest->verifyPropTypeHint(i); if (fields[i].isUnion) dest->verifyPropTypeHint(numFields); } fieldType = static_cast<TType>(transport.readI8()); } assertx(dest->assertPropTypeHints()); return dest; } void binary_serialize_internal(int8_t thrift_typeID, PHPOutputTransport& transport, const Variant& value, const FieldSpec& fieldspec) { // At this point the typeID (and field num, if applicable) should've already // been written to the output so all we need to do is write the payload. switch (thrift_typeID) { case T_STOP: case T_VOID: return; case T_STRUCT: { if (!value.is(KindOfObject)) { throw_tprotocolexception("Attempt to send non-object " "type as a T_STRUCT", INVALID_DATA); } binary_serialize_struct(value.asCObjRef(), transport); } return; case T_BOOL: transport.writeI8(value.toBoolean() ? 1 : 0); return; case T_BYTE: transport.writeI8(value.toByte()); return; case T_I16: transport.writeI16(value.toInt16()); return; case T_I32: transport.writeI32(value.toInt32()); return; case T_I64: case T_U64: transport.writeI64(value.toInt64()); return; case T_DOUBLE: { union { int64_t c; double d; } a; a.d = value.toDouble(); transport.writeI64(a.c); } return; case T_FLOAT: { union { int32_t c; float d; } a; a.d = (float)value.toDouble(); transport.writeI32(a.c); } return; //case T_UTF7: case T_UTF8: case T_UTF16: case T_STRING: { if (value.is(KindOfObject)) { throw_tprotocolexception("Attempt to send object " "type as a T_STRING", INVALID_DATA); } String sv = value.toString(); transport.writeString(sv.data(), sv.size()); } return; case T_MAP: { Array ht = value.toArray<IntishCast::Cast>(); transport.writeI8(fieldspec.ktype); transport.writeI8(fieldspec.vtype); transport.writeI32(ht.size()); auto const& key_spec = fieldspec.key(); auto const& val_spec = fieldspec.val(); for (ArrayIter key_ptr = ht.begin(); !key_ptr.end(); ++key_ptr) { binary_serialize_hashtable_key( fieldspec.ktype, transport, key_ptr.first(), key_spec); binary_serialize( fieldspec.vtype, transport, key_ptr.second(), val_spec); } } return; case T_LIST: { Array ht = value.toArray<IntishCast::Cast>(); transport.writeI8(fieldspec.vtype); transport.writeI32(ht.size()); auto const& val_spec = fieldspec.val(); for (ArrayIter key_ptr = ht.begin(); !key_ptr.end(); ++key_ptr) { binary_serialize( fieldspec.vtype, transport, key_ptr.second(), val_spec); } } return; case T_SET: { Array ht = value.toArray<IntishCast::Cast>(); transport.writeI8(fieldspec.vtype); transport.writeI32(ht.size()); auto const& val_spec = fieldspec.val(); for (ArrayIter key_ptr = ht.begin(); !key_ptr.end(); ++key_ptr) { binary_serialize_hashtable_key( fieldspec.vtype, transport, key_ptr.first(), val_spec); } } return; }; char errbuf[128]; snprintf(errbuf, sizeof(errbuf), "Unknown thrift typeID %d", thrift_typeID); throw_tprotocolexception(String(errbuf, CopyString), INVALID_DATA); } void binary_serialize(int8_t thrift_typeID, PHPOutputTransport& transport, const Variant& value, const FieldSpec& fieldspec) { const auto& thriftValue = fieldspec.adapter ? transformToThriftType(value, *fieldspec.adapter) : value; binary_serialize_internal(thrift_typeID, transport, thriftValue, fieldspec); } void binary_serialize_slow(const FieldSpec& field, const Object& obj, PHPOutputTransport& transport) { 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; transport.writeI8(fieldType); transport.writeI16(field.fieldNum); binary_serialize(fieldType, transport, fieldVal, field); } } void binary_serialize_struct(const Object& obj, PHPOutputTransport& transport) { Class* cls = obj->getVMClass(); auto prop = cls->declProperties().begin(); auto objProps = obj->props(); const size_t numProps = cls->numDeclProperties(); SpecHolder specHolder; auto const& fields = specHolder.getSpec(cls).fields; 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()); Variant fieldVal; if (fields[slot].isWrapped) { fieldVal = getThriftType(obj, StrNR(fields[slot].name)); } else { fieldVal = fieldWrapper; } if (!fieldVal.isNull()) { TType fieldType = fields[slot].type; transport.writeI8(fieldType); transport.writeI16(fields[slot].fieldNum); binary_serialize(fieldType, transport, fieldVal, fields[slot]); } else if (UNLIKELY(fieldVal.is(KindOfUninit)) && (prop[slot].attrs & AttrLateInit)) { throw_late_init_prop(prop[slot].cls, prop[slot].name, false); } } else { binary_serialize_slow(fields[slot], obj, transport); } } transport.writeI8(T_STOP); // struct end } void HHVM_FUNCTION(thrift_protocol_write_binary, const Object& transportobj, const String& method_name, int64_t msgtype, const Object& request_struct, int seqid, bool strict_write, bool oneway) { CoeffectsAutoGuard _; // Suppress class-to-string conversion warnings that occur during // serialization and deserialization. SuppressClassConversionWarning suppressor; PHPOutputTransport transport(transportobj); if (strict_write) { int32_t version = VERSION_1 | msgtype; transport.writeI32(version); transport.writeString(method_name.data(), method_name.size()); transport.writeI32(seqid); } else { transport.writeString(method_name.data(), method_name.size()); transport.writeI8(msgtype); transport.writeI32(seqid); } const Object& obj_request_struct = request_struct; binary_serialize_struct(obj_request_struct, transport); if (oneway) { transport.onewayFlush(); } else { transport.flush(); } } Object HHVM_FUNCTION(thrift_protocol_read_binary, const Object& transportobj, const String& obj_typename, bool strict_read, int options) { CoeffectsAutoGuard _; // Suppress class-to-string conversion warnings that occur during // serialization and deserialization. SuppressClassConversionWarning suppressor; VMRegAnchor _2; PHPInputTransport transport(transportobj); int8_t messageType = 0; int32_t sz = transport.readI32(); if (sz < 0) { // Check for correct version number int32_t version = sz & VERSION_MASK; if (version != VERSION_1) { char errbuf[128]; snprintf(errbuf, sizeof(errbuf), "Bad version identifier, sz=%d", sz); throw_tprotocolexception(String(errbuf, CopyString), BAD_VERSION); } messageType = (sz & 0x000000ff); int32_t namelen = transport.readI32(); // skip the name string and the sequence ID, we don't care about those transport.skip(namelen + 4); } else { if (strict_read) { char errbuf[128]; snprintf(errbuf, sizeof(errbuf), "No version identifier... " "old protocol client in strict mode? sz=%d", sz); throw_tprotocolexception(String(errbuf, CopyString), BAD_VERSION); } else { // Handle pre-versioned input transport.skip(sz); // skip string body messageType = transport.readI8(); transport.skip(4); // skip sequence number } } if (messageType == T_EXCEPTION) { throw_object( binary_deserialize_struct(s_TApplicationException, transport, options)); } return binary_deserialize_struct(obj_typename, transport, options); } Variant HHVM_FUNCTION(thrift_protocol_read_binary_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 _; PHPInputTransport transport(transportobj); return binary_deserialize_struct(obj_typename, transport, options); } /////////////////////////////////////////////////////////////////////////////// }