1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License. You may obtain a copy of the License at
9  *
10  *   http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied. See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 
20 #ifndef THRIFT_PY_PROTOCOL_TCC
21 #define THRIFT_PY_PROTOCOL_TCC
22 
23 #include <iterator>
24 
25 #define CHECK_RANGE(v, min, max) (((v) <= (max)) && ((v) >= (min)))
26 #define INIT_OUTBUF_SIZE 128
27 
28 #if PY_MAJOR_VERSION < 3
29 #include <cStringIO.h>
30 #else
31 #include <algorithm>
32 #endif
33 
34 namespace apache {
35 namespace thrift {
36 namespace py {
37 
38 #if PY_MAJOR_VERSION < 3
39 
40 namespace detail {
41 
input_check(PyObject * input)42 inline bool input_check(PyObject* input) {
43   return PycStringIO_InputCheck(input);
44 }
45 
new_encode_buffer(size_t size)46 inline EncodeBuffer* new_encode_buffer(size_t size) {
47   if (!PycStringIO) {
48     PycString_IMPORT;
49   }
50   if (!PycStringIO) {
51     return nullptr;
52   }
53   return PycStringIO->NewOutput(size);
54 }
55 
read_buffer(PyObject * buf,char ** output,int len)56 inline int read_buffer(PyObject* buf, char** output, int len) {
57   if (!PycStringIO) {
58     PycString_IMPORT;
59   }
60   if (!PycStringIO) {
61     PyErr_SetString(PyExc_ImportError, "failed to import native cStringIO");
62     return -1;
63   }
64   return PycStringIO->cread(buf, output, len);
65 }
66 }
67 
68 template <typename Impl>
~ProtocolBase()69 inline ProtocolBase<Impl>::~ProtocolBase() {
70   if (output_) {
71     Py_CLEAR(output_);
72   }
73 }
74 
75 template <typename Impl>
isUtf8(PyObject * typeargs)76 inline bool ProtocolBase<Impl>::isUtf8(PyObject* typeargs) {
77   return PyString_Check(typeargs) && !strncmp(PyString_AS_STRING(typeargs), "UTF8", 4);
78 }
79 
80 template <typename Impl>
getEncodedValue()81 PyObject* ProtocolBase<Impl>::getEncodedValue() {
82   if (!PycStringIO) {
83     PycString_IMPORT;
84   }
85   if (!PycStringIO) {
86     return nullptr;
87   }
88   return PycStringIO->cgetvalue(output_);
89 }
90 
91 template <typename Impl>
writeBuffer(char * data,size_t size)92 inline bool ProtocolBase<Impl>::writeBuffer(char* data, size_t size) {
93   if (!PycStringIO) {
94     PycString_IMPORT;
95   }
96   if (!PycStringIO) {
97     PyErr_SetString(PyExc_ImportError, "failed to import native cStringIO");
98     return false;
99   }
100   int len = PycStringIO->cwrite(output_, data, size);
101   if (len < 0) {
102     PyErr_SetString(PyExc_IOError, "failed to write to cStringIO object");
103     return false;
104   }
105   if (static_cast<size_t>(len) != size) {
106     PyErr_Format(PyExc_EOFError, "write length mismatch: expected %lu got %d", size, len);
107     return false;
108   }
109   return true;
110 }
111 
112 #else
113 
114 namespace detail {
115 
116 inline bool input_check(PyObject* input) {
117   // TODO: Check for BytesIO type
118   return true;
119 }
120 
121 inline EncodeBuffer* new_encode_buffer(size_t size) {
122   EncodeBuffer* buffer = new EncodeBuffer;
123   buffer->buf.reserve(size);
124   buffer->pos = 0;
125   return buffer;
126 }
127 
128 struct bytesio {
129   PyObject_HEAD
130 #if PY_MINOR_VERSION < 5
131       char* buf;
132 #else
133       PyObject* buf;
134 #endif
135   Py_ssize_t pos;
136   Py_ssize_t string_size;
137 };
138 
139 inline int read_buffer(PyObject* buf, char** output, int len) {
140   bytesio* buf2 = reinterpret_cast<bytesio*>(buf);
141 #if PY_MINOR_VERSION < 5
142   *output = buf2->buf + buf2->pos;
143 #else
144   *output = PyBytes_AS_STRING(buf2->buf) + buf2->pos;
145 #endif
146   Py_ssize_t pos0 = buf2->pos;
147   buf2->pos = (std::min)(buf2->pos + static_cast<Py_ssize_t>(len), buf2->string_size);
148   return static_cast<int>(buf2->pos - pos0);
149 }
150 }
151 
152 template <typename Impl>
153 inline ProtocolBase<Impl>::~ProtocolBase() {
154   if (output_) {
155     delete output_;
156   }
157 }
158 
159 template <typename Impl>
160 inline bool ProtocolBase<Impl>::isUtf8(PyObject* typeargs) {
161   // while condition for py2 is "arg == 'UTF8'", it should be "arg != 'BINARY'" for py3.
162   // HACK: check the length and don't bother reading the value
163   return !PyUnicode_Check(typeargs) || PyUnicode_GET_LENGTH(typeargs) != 6;
164 }
165 
166 template <typename Impl>
167 PyObject* ProtocolBase<Impl>::getEncodedValue() {
168   return PyBytes_FromStringAndSize(output_->buf.data(), output_->buf.size());
169 }
170 
171 template <typename Impl>
172 inline bool ProtocolBase<Impl>::writeBuffer(char* data, size_t size) {
173   size_t need = size + output_->pos;
174   if (output_->buf.capacity() < need) {
175     try {
176       output_->buf.reserve(need);
177     } catch (std::bad_alloc&) {
178       PyErr_SetString(PyExc_MemoryError, "Failed to allocate write buffer");
179       return false;
180     }
181   }
182   std::copy(data, data + size, std::back_inserter(output_->buf));
183   return true;
184 }
185 
186 #endif
187 
188 namespace detail {
189 
190 #define DECLARE_OP_SCOPE(name, op)                                                                 \
191   template <typename Impl>                                                                         \
192   struct name##Scope {                                                                             \
193     Impl* impl;                                                                                    \
194     bool valid;                                                                                    \
195     name##Scope(Impl* thiz) : impl(thiz), valid(impl->op##Begin()) {}                              \
196     ~name##Scope() {                                                                               \
197       if (valid)                                                                                   \
198         impl->op##End();                                                                           \
199     }                                                                                              \
200     operator bool() { return valid; }                                                              \
201   };                                                                                               \
202   template <typename Impl, template <typename> class T>                                            \
203   name##Scope<Impl> op##Scope(T<Impl>* thiz) {                                                     \
204     return name##Scope<Impl>(static_cast<Impl*>(thiz));                                            \
205   }
DECLARE_OP_SCOPE(WriteStruct,writeStruct)206 DECLARE_OP_SCOPE(WriteStruct, writeStruct)
207 DECLARE_OP_SCOPE(ReadStruct, readStruct)
208 #undef DECLARE_OP_SCOPE
209 
210 inline bool check_ssize_t_32(Py_ssize_t len) {
211   // error from getting the int
212   if (INT_CONV_ERROR_OCCURRED(len)) {
213     return false;
214   }
215   if (!CHECK_RANGE(len, 0, (std::numeric_limits<int32_t>::max)())) {
216     PyErr_SetString(PyExc_OverflowError, "size out of range: exceeded INT32_MAX");
217     return false;
218   }
219   return true;
220 }
221 }
222 
223 template <typename T>
parse_pyint(PyObject * o,T * ret,int32_t min,int32_t max)224 bool parse_pyint(PyObject* o, T* ret, int32_t min, int32_t max) {
225   long val = PyInt_AsLong(o);
226 
227   if (INT_CONV_ERROR_OCCURRED(val)) {
228     return false;
229   }
230   if (!CHECK_RANGE(val, min, max)) {
231     PyErr_SetString(PyExc_OverflowError, "int out of range");
232     return false;
233   }
234 
235   *ret = static_cast<T>(val);
236   return true;
237 }
238 
239 template <typename Impl>
checkType(TType got,TType expected)240 inline bool ProtocolBase<Impl>::checkType(TType got, TType expected) {
241   if (expected != got) {
242     PyErr_SetString(PyExc_TypeError, "got wrong ttype while reading field");
243     return false;
244   }
245   return true;
246 }
247 
248 template <typename Impl>
checkLengthLimit(int32_t len,long limit)249 bool ProtocolBase<Impl>::checkLengthLimit(int32_t len, long limit) {
250   if (len < 0) {
251     PyErr_Format(PyExc_OverflowError, "negative length: %ld", limit);
252     return false;
253   }
254   if (len > limit) {
255     PyErr_Format(PyExc_OverflowError, "size exceeded specified limit: %ld", limit);
256     return false;
257   }
258   return true;
259 }
260 
261 template <typename Impl>
readBytes(char ** output,int len)262 bool ProtocolBase<Impl>::readBytes(char** output, int len) {
263   if (len < 0) {
264     PyErr_Format(PyExc_ValueError, "attempted to read negative length: %d", len);
265     return false;
266   }
267   // TODO(dreiss): Don't fear the malloc.  Think about taking a copy of
268   //               the partial read instead of forcing the transport
269   //               to prepend it to its buffer.
270 
271   int rlen = detail::read_buffer(input_.stringiobuf.get(), output, len);
272 
273   if (rlen == len) {
274     return true;
275   } else if (rlen == -1) {
276     return false;
277   } else {
278     // using building functions as this is a rare codepath
279     ScopedPyObject newiobuf(PyObject_CallFunction(input_.refill_callable.get(), refill_signature,
280                                                   *output, rlen, len, nullptr));
281     if (!newiobuf) {
282       return false;
283     }
284 
285     // must do this *AFTER* the call so that we don't deref the io buffer
286     input_.stringiobuf.reset(newiobuf.release());
287 
288     rlen = detail::read_buffer(input_.stringiobuf.get(), output, len);
289 
290     if (rlen == len) {
291       return true;
292     } else if (rlen == -1) {
293       return false;
294     } else {
295       // TODO(dreiss): This could be a valid code path for big binary blobs.
296       PyErr_SetString(PyExc_TypeError, "refill claimed to have refilled the buffer, but didn't!!");
297       return false;
298     }
299   }
300 }
301 
302 template <typename Impl>
prepareDecodeBufferFromTransport(PyObject * trans)303 bool ProtocolBase<Impl>::prepareDecodeBufferFromTransport(PyObject* trans) {
304   if (input_.stringiobuf) {
305     PyErr_SetString(PyExc_ValueError, "decode buffer is already initialized");
306     return false;
307   }
308 
309   ScopedPyObject stringiobuf(PyObject_GetAttr(trans, INTERN_STRING(cstringio_buf)));
310   if (!stringiobuf) {
311     return false;
312   }
313   if (!detail::input_check(stringiobuf.get())) {
314     PyErr_SetString(PyExc_TypeError, "expecting stringio input_");
315     return false;
316   }
317 
318   ScopedPyObject refill_callable(PyObject_GetAttr(trans, INTERN_STRING(cstringio_refill)));
319   if (!refill_callable) {
320     return false;
321   }
322   if (!PyCallable_Check(refill_callable.get())) {
323     PyErr_SetString(PyExc_TypeError, "expecting callable");
324     return false;
325   }
326 
327   input_.stringiobuf.swap(stringiobuf);
328   input_.refill_callable.swap(refill_callable);
329   return true;
330 }
331 
332 template <typename Impl>
prepareEncodeBuffer()333 bool ProtocolBase<Impl>::prepareEncodeBuffer() {
334   output_ = detail::new_encode_buffer(INIT_OUTBUF_SIZE);
335   return output_ != nullptr;
336 }
337 
338 template <typename Impl>
encodeValue(PyObject * value,TType type,PyObject * typeargs)339 bool ProtocolBase<Impl>::encodeValue(PyObject* value, TType type, PyObject* typeargs) {
340   /*
341    * Refcounting Strategy:
342    *
343    * We assume that elements of the thrift_spec tuple are not going to be
344    * mutated, so we don't ref count those at all. Other than that, we try to
345    * keep a reference to all the user-created objects while we work with them.
346    * encodeValue assumes that a reference is already held. The *caller* is
347    * responsible for handling references
348    */
349 
350   switch (type) {
351 
352   case T_BOOL: {
353     int v = PyObject_IsTrue(value);
354     if (v == -1) {
355       return false;
356     }
357     impl()->writeBool(v);
358     return true;
359   }
360   case T_I08: {
361     int8_t val;
362 
363     if (!parse_pyint(value, &val, (std::numeric_limits<int8_t>::min)(),
364                      (std::numeric_limits<int8_t>::max)())) {
365       return false;
366     }
367 
368     impl()->writeI8(val);
369     return true;
370   }
371   case T_I16: {
372     int16_t val;
373 
374     if (!parse_pyint(value, &val, (std::numeric_limits<int16_t>::min)(),
375                      (std::numeric_limits<int16_t>::max)())) {
376       return false;
377     }
378 
379     impl()->writeI16(val);
380     return true;
381   }
382   case T_I32: {
383     int32_t val;
384 
385     if (!parse_pyint(value, &val, (std::numeric_limits<int32_t>::min)(),
386                      (std::numeric_limits<int32_t>::max)())) {
387       return false;
388     }
389 
390     impl()->writeI32(val);
391     return true;
392   }
393   case T_I64: {
394     int64_t nval = PyLong_AsLongLong(value);
395 
396     if (INT_CONV_ERROR_OCCURRED(nval)) {
397       return false;
398     }
399 
400     if (!CHECK_RANGE(nval, (std::numeric_limits<int64_t>::min)(),
401                      (std::numeric_limits<int64_t>::max)())) {
402       PyErr_SetString(PyExc_OverflowError, "int out of range");
403       return false;
404     }
405 
406     impl()->writeI64(nval);
407     return true;
408   }
409 
410   case T_DOUBLE: {
411     double nval = PyFloat_AsDouble(value);
412     if (nval == -1.0 && PyErr_Occurred()) {
413       return false;
414     }
415 
416     impl()->writeDouble(nval);
417     return true;
418   }
419 
420   case T_STRING: {
421     ScopedPyObject nval;
422 
423     if (PyUnicode_Check(value)) {
424       nval.reset(PyUnicode_AsUTF8String(value));
425       if (!nval) {
426         return false;
427       }
428     } else {
429       Py_INCREF(value);
430       nval.reset(value);
431     }
432 
433     Py_ssize_t len = PyBytes_Size(nval.get());
434     if (!detail::check_ssize_t_32(len)) {
435       return false;
436     }
437 
438     impl()->writeString(nval.get(), static_cast<int32_t>(len));
439     return true;
440   }
441 
442   case T_LIST:
443   case T_SET: {
444     SetListTypeArgs parsedargs;
445     if (!parse_set_list_args(&parsedargs, typeargs)) {
446       return false;
447     }
448 
449     Py_ssize_t len = PyObject_Length(value);
450     if (!detail::check_ssize_t_32(len)) {
451       return false;
452     }
453 
454     if (!impl()->writeListBegin(value, parsedargs, static_cast<int32_t>(len)) || PyErr_Occurred()) {
455       return false;
456     }
457     ScopedPyObject iterator(PyObject_GetIter(value));
458     if (!iterator) {
459       return false;
460     }
461 
462     while (PyObject* rawItem = PyIter_Next(iterator.get())) {
463       ScopedPyObject item(rawItem);
464       if (!encodeValue(item.get(), parsedargs.element_type, parsedargs.typeargs)) {
465         return false;
466       }
467     }
468 
469     return true;
470   }
471 
472   case T_MAP: {
473     Py_ssize_t len = PyDict_Size(value);
474     if (!detail::check_ssize_t_32(len)) {
475       return false;
476     }
477 
478     MapTypeArgs parsedargs;
479     if (!parse_map_args(&parsedargs, typeargs)) {
480       return false;
481     }
482 
483     if (!impl()->writeMapBegin(value, parsedargs, static_cast<int32_t>(len)) || PyErr_Occurred()) {
484       return false;
485     }
486     Py_ssize_t pos = 0;
487     PyObject* k = nullptr;
488     PyObject* v = nullptr;
489     // TODO(bmaurer): should support any mapping, not just dicts
490     while (PyDict_Next(value, &pos, &k, &v)) {
491       if (!encodeValue(k, parsedargs.ktag, parsedargs.ktypeargs)
492           || !encodeValue(v, parsedargs.vtag, parsedargs.vtypeargs)) {
493         return false;
494       }
495     }
496     return true;
497   }
498 
499   case T_STRUCT: {
500     StructTypeArgs parsedargs;
501     if (!parse_struct_args(&parsedargs, typeargs)) {
502       return false;
503     }
504 
505     Py_ssize_t nspec = PyTuple_Size(parsedargs.spec);
506     if (nspec == -1) {
507       PyErr_SetString(PyExc_TypeError, "spec is not a tuple");
508       return false;
509     }
510 
511     detail::WriteStructScope<Impl> scope = detail::writeStructScope(this);
512     if (!scope) {
513       return false;
514     }
515     for (Py_ssize_t i = 0; i < nspec; i++) {
516       PyObject* spec_tuple = PyTuple_GET_ITEM(parsedargs.spec, i);
517       if (spec_tuple == Py_None) {
518         continue;
519       }
520 
521       StructItemSpec parsedspec;
522       if (!parse_struct_item_spec(&parsedspec, spec_tuple)) {
523         return false;
524       }
525 
526       ScopedPyObject instval(PyObject_GetAttr(value, parsedspec.attrname));
527 
528       if (!instval) {
529         return false;
530       }
531 
532       if (instval.get() == Py_None) {
533         continue;
534       }
535 
536       bool res = impl()->writeField(instval.get(), parsedspec);
537       if (!res) {
538         return false;
539       }
540     }
541     impl()->writeFieldStop();
542     return true;
543   }
544 
545   case T_STOP:
546   case T_VOID:
547   case T_UTF16:
548   case T_UTF8:
549   case T_U64:
550   default:
551     PyErr_Format(PyExc_TypeError, "Unexpected TType for encodeValue: %d", type);
552     return false;
553   }
554 
555   return true;
556 }
557 
558 template <typename Impl>
skip(TType type)559 bool ProtocolBase<Impl>::skip(TType type) {
560   switch (type) {
561   case T_BOOL:
562     return impl()->skipBool();
563   case T_I08:
564     return impl()->skipByte();
565   case T_I16:
566     return impl()->skipI16();
567   case T_I32:
568     return impl()->skipI32();
569   case T_I64:
570     return impl()->skipI64();
571   case T_DOUBLE:
572     return impl()->skipDouble();
573 
574   case T_STRING: {
575     return impl()->skipString();
576   }
577 
578   case T_LIST:
579   case T_SET: {
580     TType etype = T_STOP;
581     int32_t len = impl()->readListBegin(etype);
582     if (len < 0) {
583       return false;
584     }
585     for (int32_t i = 0; i < len; i++) {
586       if (!skip(etype)) {
587         return false;
588       }
589     }
590     return true;
591   }
592 
593   case T_MAP: {
594     TType ktype = T_STOP;
595     TType vtype = T_STOP;
596     int32_t len = impl()->readMapBegin(ktype, vtype);
597     if (len < 0) {
598       return false;
599     }
600     for (int32_t i = 0; i < len; i++) {
601       if (!skip(ktype) || !skip(vtype)) {
602         return false;
603       }
604     }
605     return true;
606   }
607 
608   case T_STRUCT: {
609     detail::ReadStructScope<Impl> scope = detail::readStructScope(this);
610     if (!scope) {
611       return false;
612     }
613     while (true) {
614       TType type = T_STOP;
615       int16_t tag;
616       if (!impl()->readFieldBegin(type, tag)) {
617         return false;
618       }
619       if (type == T_STOP) {
620         return true;
621       }
622       if (!skip(type)) {
623         return false;
624       }
625     }
626     return true;
627   }
628 
629   case T_STOP:
630   case T_VOID:
631   case T_UTF16:
632   case T_UTF8:
633   case T_U64:
634   default:
635     PyErr_Format(PyExc_TypeError, "Unexpected TType for skip: %d", type);
636     return false;
637   }
638 
639   return true;
640 }
641 
642 // Returns a new reference.
643 template <typename Impl>
decodeValue(TType type,PyObject * typeargs)644 PyObject* ProtocolBase<Impl>::decodeValue(TType type, PyObject* typeargs) {
645   switch (type) {
646 
647   case T_BOOL: {
648     bool v = 0;
649     if (!impl()->readBool(v)) {
650       return nullptr;
651     }
652     if (v) {
653       Py_RETURN_TRUE;
654     } else {
655       Py_RETURN_FALSE;
656     }
657   }
658   case T_I08: {
659     int8_t v = 0;
660     if (!impl()->readI8(v)) {
661       return nullptr;
662     }
663     return PyInt_FromLong(v);
664   }
665   case T_I16: {
666     int16_t v = 0;
667     if (!impl()->readI16(v)) {
668       return nullptr;
669     }
670     return PyInt_FromLong(v);
671   }
672   case T_I32: {
673     int32_t v = 0;
674     if (!impl()->readI32(v)) {
675       return nullptr;
676     }
677     return PyInt_FromLong(v);
678   }
679 
680   case T_I64: {
681     int64_t v = 0;
682     if (!impl()->readI64(v)) {
683       return nullptr;
684     }
685     // TODO(dreiss): Find out if we can take this fastpath always when
686     //               sizeof(long) == sizeof(long long).
687     if (CHECK_RANGE(v, LONG_MIN, LONG_MAX)) {
688       return PyInt_FromLong((long)v);
689     }
690     return PyLong_FromLongLong(v);
691   }
692 
693   case T_DOUBLE: {
694     double v = 0.0;
695     if (!impl()->readDouble(v)) {
696       return nullptr;
697     }
698     return PyFloat_FromDouble(v);
699   }
700 
701   case T_STRING: {
702     char* buf = nullptr;
703     int len = impl()->readString(&buf);
704     if (len < 0) {
705       return nullptr;
706     }
707     if (isUtf8(typeargs)) {
708       return PyUnicode_DecodeUTF8(buf, len, "replace");
709     } else {
710       return PyBytes_FromStringAndSize(buf, len);
711     }
712   }
713 
714   case T_LIST:
715   case T_SET: {
716     SetListTypeArgs parsedargs;
717     if (!parse_set_list_args(&parsedargs, typeargs)) {
718       return nullptr;
719     }
720 
721     TType etype = T_STOP;
722     int32_t len = impl()->readListBegin(etype);
723     if (len < 0) {
724       return nullptr;
725     }
726     if (len > 0 && !checkType(etype, parsedargs.element_type)) {
727       return nullptr;
728     }
729 
730     bool use_tuple = type == T_LIST && parsedargs.immutable;
731     ScopedPyObject ret(use_tuple ? PyTuple_New(len) : PyList_New(len));
732     if (!ret) {
733       return nullptr;
734     }
735 
736     for (int i = 0; i < len; i++) {
737       PyObject* item = decodeValue(etype, parsedargs.typeargs);
738       if (!item) {
739         return nullptr;
740       }
741       if (use_tuple) {
742         PyTuple_SET_ITEM(ret.get(), i, item);
743       } else {
744         PyList_SET_ITEM(ret.get(), i, item);
745       }
746     }
747 
748     // TODO(dreiss): Consider biting the bullet and making two separate cases
749     //               for list and set, avoiding this post facto conversion.
750     if (type == T_SET) {
751       PyObject* setret;
752       setret = parsedargs.immutable ? PyFrozenSet_New(ret.get()) : PySet_New(ret.get());
753       return setret;
754     }
755     return ret.release();
756   }
757 
758   case T_MAP: {
759     MapTypeArgs parsedargs;
760     if (!parse_map_args(&parsedargs, typeargs)) {
761       return nullptr;
762     }
763 
764     TType ktype = T_STOP;
765     TType vtype = T_STOP;
766     uint32_t len = impl()->readMapBegin(ktype, vtype);
767     if (len > 0 && (!checkType(ktype, parsedargs.ktag) || !checkType(vtype, parsedargs.vtag))) {
768       return nullptr;
769     }
770 
771     ScopedPyObject ret(PyDict_New());
772     if (!ret) {
773       return nullptr;
774     }
775 
776     for (uint32_t i = 0; i < len; i++) {
777       ScopedPyObject k(decodeValue(ktype, parsedargs.ktypeargs));
778       if (!k) {
779         return nullptr;
780       }
781       ScopedPyObject v(decodeValue(vtype, parsedargs.vtypeargs));
782       if (!v) {
783         return nullptr;
784       }
785       if (PyDict_SetItem(ret.get(), k.get(), v.get()) == -1) {
786         return nullptr;
787       }
788     }
789 
790     if (parsedargs.immutable) {
791       if (!ThriftModule) {
792         ThriftModule = PyImport_ImportModule("thrift.Thrift");
793       }
794       if (!ThriftModule) {
795         return nullptr;
796       }
797 
798       ScopedPyObject cls(PyObject_GetAttr(ThriftModule, INTERN_STRING(TFrozenDict)));
799       if (!cls) {
800         return nullptr;
801       }
802 
803       ScopedPyObject arg(PyTuple_New(1));
804       PyTuple_SET_ITEM(arg.get(), 0, ret.release());
805       ret.reset(PyObject_CallObject(cls.get(), arg.get()));
806     }
807 
808     return ret.release();
809   }
810 
811   case T_STRUCT: {
812     StructTypeArgs parsedargs;
813     if (!parse_struct_args(&parsedargs, typeargs)) {
814       return nullptr;
815     }
816     return readStruct(Py_None, parsedargs.klass, parsedargs.spec);
817   }
818 
819   case T_STOP:
820   case T_VOID:
821   case T_UTF16:
822   case T_UTF8:
823   case T_U64:
824   default:
825     PyErr_Format(PyExc_TypeError, "Unexpected TType for decodeValue: %d", type);
826     return nullptr;
827   }
828 }
829 
830 template <typename Impl>
readStruct(PyObject * output,PyObject * klass,PyObject * spec_seq)831 PyObject* ProtocolBase<Impl>::readStruct(PyObject* output, PyObject* klass, PyObject* spec_seq) {
832   int spec_seq_len = PyTuple_Size(spec_seq);
833   bool immutable = output == Py_None;
834   ScopedPyObject kwargs;
835   if (spec_seq_len == -1) {
836     return nullptr;
837   }
838 
839   if (immutable) {
840     kwargs.reset(PyDict_New());
841     if (!kwargs) {
842       PyErr_SetString(PyExc_TypeError, "failed to prepare kwargument storage");
843       return nullptr;
844     }
845   }
846 
847   detail::ReadStructScope<Impl> scope = detail::readStructScope(this);
848   if (!scope) {
849     return nullptr;
850   }
851   while (true) {
852     TType type = T_STOP;
853     int16_t tag;
854     if (!impl()->readFieldBegin(type, tag)) {
855       return nullptr;
856     }
857     if (type == T_STOP) {
858       break;
859     }
860     if (tag < 0 || tag >= spec_seq_len) {
861       if (!skip(type)) {
862         PyErr_SetString(PyExc_TypeError, "Error while skipping unknown field");
863         return nullptr;
864       }
865       continue;
866     }
867 
868     PyObject* item_spec = PyTuple_GET_ITEM(spec_seq, tag);
869     if (item_spec == Py_None) {
870       if (!skip(type)) {
871         PyErr_SetString(PyExc_TypeError, "Error while skipping unknown field");
872         return nullptr;
873       }
874       continue;
875     }
876     StructItemSpec parsedspec;
877     if (!parse_struct_item_spec(&parsedspec, item_spec)) {
878       return nullptr;
879     }
880     if (parsedspec.type != type) {
881       if (!skip(type)) {
882         PyErr_Format(PyExc_TypeError, "struct field had wrong type: expected %d but got %d",
883                      parsedspec.type, type);
884         return nullptr;
885       }
886       continue;
887     }
888 
889     ScopedPyObject fieldval(decodeValue(parsedspec.type, parsedspec.typeargs));
890     if (!fieldval) {
891       return nullptr;
892     }
893 
894     if ((immutable && PyDict_SetItem(kwargs.get(), parsedspec.attrname, fieldval.get()) == -1)
895         || (!immutable && PyObject_SetAttr(output, parsedspec.attrname, fieldval.get()) == -1)) {
896       return nullptr;
897     }
898   }
899   if (immutable) {
900     ScopedPyObject args(PyTuple_New(0));
901     if (!args) {
902       PyErr_SetString(PyExc_TypeError, "failed to prepare argument storage");
903       return nullptr;
904     }
905     return PyObject_Call(klass, args.get(), kwargs.get());
906   }
907   Py_INCREF(output);
908   return output;
909 }
910 }
911 }
912 }
913 #endif // THRIFT_PY_PROTOCOL_H
914