improve decimal support
[awesomized/ext-ion] / ion_private.h
1 /*
2 +--------------------------------------------------------------------+
3 | PECL :: ion |
4 +--------------------------------------------------------------------+
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted provided that the conditions mentioned |
7 | in the accompanying LICENSE file are met. |
8 +--------------------------------------------------------------------+
9 | Copyright (c) 2021, Michael Wallner <mike@php.net> |
10 +--------------------------------------------------------------------+
11 */
12
13 #include "ionc/ion.h"
14
15 #include "php.h"
16 #include "ext/date/php_date.h"
17
18 typedef struct php_ion_global_serializer php_ion_global_serializer;
19 typedef struct php_ion_global_unserializer php_ion_global_unserializer;
20
21 ZEND_BEGIN_MODULE_GLOBALS(ion)
22
23 struct php_ion_global_serializer {
24 HashTable ids;
25 HashTable tmp;
26 uint64_t level;
27 } serializer;
28
29 struct php_ion_global_unserializer {
30 HashTable ids;
31 HashTable tmp;
32 HashTable addref;
33 uint64_t level;
34 } unserializer;
35
36 ZEND_END_MODULE_GLOBALS(ion);
37
38 #ifdef ZTS
39 # define php_ion_globals (*((zend_ion_globals *) (*((void ***) tsrm_get_ls_cache()))[TSRM_UNSHUFFLE_RSRC_ID(ion_globals_id)]))
40 #else
41 # define php_ion_globals ion_globals
42 #endif
43
44 ZEND_DECLARE_MODULE_GLOBALS(ion);
45
46 static inline void php_ion_globals_serializer_init(void)
47 {
48 php_ion_global_serializer *s = &php_ion_globals.serializer;
49
50 zend_hash_init(&s->tmp, 0, NULL, ZVAL_PTR_DTOR, 0);
51 zend_hash_init(&s->ids, 0, NULL, NULL, 0);
52 }
53
54 static inline void php_ion_globals_serializer_step(void)
55 {
56 php_ion_global_serializer *s = &php_ion_globals.serializer;
57
58 if (!s->level++) {
59 zend_hash_clean(&s->ids);
60 zend_hash_clean(&s->tmp);
61 }
62 }
63
64 static inline void php_ion_globals_serializer_exit(void)
65 {
66 php_ion_global_serializer *s = &php_ion_globals.serializer;
67
68 ZEND_ASSERT(s->level);
69 if (!--s->level) {
70 zend_hash_clean(&s->ids);
71 zend_hash_clean(&s->tmp);
72 }
73 }
74
75 static inline void php_ion_globals_serializer_dtor(void)
76 {
77 php_ion_global_serializer *s = &php_ion_globals.serializer;
78
79 zend_hash_destroy(&s->tmp);
80 zend_hash_destroy(&s->ids);
81 }
82
83 void ZVAL_ADDREF(zval *zv)
84 {
85 if (Z_ISREF_P(zv)) {
86 Z_TRY_ADDREF_P(Z_REFVAL_P(zv));
87 } else {
88 Z_TRY_ADDREF_P(zv);
89 }
90 }
91 static inline void php_ion_globals_unserializer_init(void)
92 {
93 php_ion_global_unserializer *s = &php_ion_globals.unserializer;
94
95 zend_hash_init(&s->tmp, 0, NULL, ZVAL_PTR_DTOR, 0);
96 zend_hash_init(&s->ids, 0, NULL, NULL, 0);
97 zend_hash_init(&s->addref, 0, NULL, ZVAL_ADDREF, 0);
98
99 }
100
101 static inline void php_ion_globals_unserializer_step(void)
102 {
103 php_ion_global_unserializer *s = &php_ion_globals.unserializer;
104
105 if (!s->level++) {
106 zend_hash_clean(&s->addref);
107 zend_hash_clean(&s->ids);
108 zend_hash_clean(&s->tmp);
109 }
110 }
111
112 static inline void php_ion_globals_unserializer_exit(void)
113 {
114 php_ion_global_unserializer *s = &php_ion_globals.unserializer;
115
116 ZEND_ASSERT(s->level);
117 if (!--s->level) {
118 zend_hash_clean(&s->addref);
119 zend_hash_clean(&s->ids);
120 zend_hash_clean(&s->tmp);
121 }
122 }
123
124 static inline void php_ion_globals_unserializer_dtor(void)
125 {
126 php_ion_global_unserializer *s = &php_ion_globals.unserializer;
127
128 zend_hash_destroy(&s->addref);
129 zend_hash_destroy(&s->ids);
130 zend_hash_destroy(&s->tmp);
131 }
132
133 static zend_class_entry
134 *ce_Annotation,
135 *ce_Catalog,
136 *ce_Collection,
137 *ce_Decimal,
138 *ce_Decimal_Context,
139 *ce_PHP_Serializer,
140 *ce_PHP_Unserializer,
141 *ce_Reader,
142 *ce_Reader_Options,
143 *ce_Reader_Reader,
144 *ce_Reader_Buffer,
145 *ce_Reader_Stream,
146 *ce_Reader_Buffer_Reader,
147 *ce_Reader_Stream_Reader,
148 *ce_Symbol,
149 *ce_Symbol_ImportLocation,
150 *ce_Symbol_System,
151 *ce_Symbol_System_SID,
152 *ce_Symbol_Table,
153 *ce_Timestamp,
154 *ce_Type,
155 *ce_Writer,
156 *ce_Writer_Options,
157 *ce_Writer_Buffer,
158 *ce_Writer_Buffer_Writer,
159 *ce_Writer_Stream,
160 *ce_Writer_Stream_Writer,
161 *ce_Writer_Writer
162 ;
163 static zend_object_handlers
164 oh_catalog,
165 oh_decimal,
166 oh_decimal_ctx,
167 oh_reader_options,
168 oh_reader,
169 oh_symbol,
170 oh_symbol_iloc,
171 oh_symbol_table,
172 oh_timestamp,
173 oh_type,
174 oh_writer_options,
175 oh_writer
176 ;
177
178 #define php_ion_decl(type, name, ...) \
179 static zend_object *create_ion_ ## name(zend_class_entry *ce) \
180 { \
181 if (!ce) ce = ce_ ## name; \
182 php_ion_ ## type *o = ecalloc(1, sizeof(*o) + zend_object_properties_size(ce)); \
183 zend_object_std_init(&o->std, ce); \
184 object_properties_init(&o->std, ce); \
185 o->std.handlers = &oh_ ## type; \
186 return &o->std; \
187 } \
188 static void free_ion_ ## name(zend_object *std) \
189 { \
190 php_ion_ ## type *obj = php_ion_obj(type, std); \
191 __VA_ARGS__; \
192 zend_object_std_dtor(std); \
193 (void) obj; \
194 }
195 #define php_ion_register(type, name, ...) do { \
196 ce_ ## name = register_class_ion_ ## name(__VA_ARGS__); \
197 ce_ ## name ->create_object = create_ion_ ## name; \
198 memcpy(&oh_ ## type, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); \
199 oh_ ## type .offset = offsetof(php_ion_ ## type, std); \
200 oh_ ## type .free_obj = free_ion_ ## name; \
201 } while (0)
202
203 #define php_ion_obj(type, obj) \
204 ((php_ion_ ## type *) (obj ? ((char *)(obj) - XtOffsetOf(php_ion_ ## type, std)) : NULL))
205
206 #define ION_CHECK(err, ...) do { \
207 iERR __err = err; \
208 if (__err) { \
209 zend_throw_exception_ex(spl_ce_RuntimeException, __err, "%s: %s", ion_error_to_str(__err), #err); \
210 __VA_ARGS__; \
211 return; \
212 } \
213 } while (0)
214
215 #define ION_CATCH(...) do { \
216 if (EG(exception)) { \
217 __VA_ARGS__; \
218 return; \
219 } \
220 } while (0)
221
222 #define PTR_CHECK(ptr, ...) do { \
223 if (!(ptr)) { \
224 zend_throw_error(NULL, "Uninitialized object"); \
225 __VA_ARGS__; \
226 return; \
227 } \
228 } while (0)
229
230 #define OBJ_CHECK(obj) do { \
231 PTR_CHECK(obj); \
232 PTR_CHECK(*((void **)obj)); \
233 } while (0)
234
235 static inline ION_STRING *ion_string_from_cstr(ION_STRING *is, const char *s, size_t l)
236 {
237 is->length = l;
238 is->value = (BYTE *) s;
239 return is;
240 }
241
242 static inline ION_STRING *ion_string_from_zend(ION_STRING *is, const zend_string *zs)
243 {
244 is->length = ZSTR_LEN(zs);
245 is->value = (BYTE *) ZSTR_VAL(zs);
246 return is;
247 }
248
249 static inline zend_string *zend_string_from_ion(const ION_STRING *s)
250 {
251 return zend_string_init((const char *) s->value, s->length, 0);
252 }
253
254 static inline void update_property_obj(zend_object *obj, const char *n, size_t l, zend_object *p)
255 {
256 zval zobj;
257 ZVAL_OBJ(&zobj, p);
258 zend_update_property(obj->ce, obj, n, l, &zobj);
259 }
260
261 typedef struct php_ion_type {
262 ION_TYPE typ;
263 zend_object std;
264 } php_ion_type;
265
266 #define RETURN_IONTYPE(typ) do { \
267 zend_object *__zo = php_ion_type_fetch(typ); \
268 if (UNEXPECTED(!__zo)) { \
269 RETURN_THROWS(); \
270 } \
271 RETURN_OBJ_COPY(__zo); \
272 } while(0)
273
274 static inline zend_object *php_ion_type_fetch(ION_TYPE typ)
275 {
276 zend_long index = ION_TYPE_INT(typ);
277 zval *ztype = zend_hash_index_find(ce_Type->backed_enum_table, index);
278
279 if (UNEXPECTED(!ztype || Z_TYPE_P(ztype) != IS_STRING)) {
280 zend_value_error(ZEND_LONG_FMT " is not a valid backing value for enum \"%s\"", index, ZSTR_VAL(ce_Type->name));
281 return NULL;
282 }
283 return zend_enum_get_case(ce_Type, Z_STR_P(ztype));
284 }
285
286 typedef struct php_ion_symbol_iloc {
287 ION_SYMBOL_IMPORT_LOCATION loc;
288 zend_string *name;
289 zend_object std;
290 } php_ion_symbol_iloc;
291
292 static inline void php_ion_symbol_iloc_ctor(php_ion_symbol_iloc *obj)
293 {
294 zend_update_property_long(obj->std.ce, &obj->std, ZEND_STRL("location"), obj->loc.location);
295 zend_update_property_str(obj->std.ce, &obj->std, ZEND_STRL("name"), obj->name);
296 ion_string_from_zend(&obj->loc.name, obj->name);
297 }
298
299 static inline void php_ion_symbol_iloc_dtor(php_ion_symbol_iloc *obj)
300 {
301 zend_string_release(obj->name);
302 }
303
304 typedef struct php_ion_symbol {
305 ION_SYMBOL sym;
306 zend_string *value;
307 zend_object *iloc, std;
308 } php_ion_symbol;
309
310 static inline void php_ion_symbol_ctor(php_ion_symbol *obj)
311 {
312 zend_update_property_long(obj->std.ce, &obj->std, ZEND_STRL("sid"),
313 obj->sym.sid);
314 zend_update_property_str(obj->std.ce, &obj->std, ZEND_STRL("value"), obj->value);
315 ion_string_from_zend(&obj->sym.value, obj->value);
316 if (obj->iloc) {
317 update_property_obj(&obj->std, ZEND_STRL("importLocation"), obj->iloc);
318 obj->sym.import_location = php_ion_obj(symbol_iloc, obj->iloc)->loc;
319 } else {
320 zend_update_property_null(obj->std.ce, &obj->std, ZEND_STRL("importLocation"));
321 }
322 }
323
324 static inline void php_ion_symbol_dtor(php_ion_symbol *obj)
325 {
326 zend_string_release(obj->value);
327 }
328
329 static inline void php_ion_symbol_zval(ION_SYMBOL *sym_ptr, zval *return_value)
330 {
331 object_init_ex(return_value, ce_Symbol);
332 php_ion_symbol *sym = php_ion_obj(symbol, Z_OBJ_P(return_value));
333
334 sym->sym.sid = sym_ptr->sid;
335 sym->value = zend_string_from_ion(&sym_ptr->value);
336 if (!ION_SYMBOL_IMPORT_LOCATION_IS_NULL(sym_ptr)) {
337 zval ziloc;
338 object_init_ex(&ziloc, ce_Symbol_ImportLocation);
339 sym->iloc = Z_OBJ(ziloc);
340
341 php_ion_symbol_iloc *iloc = php_ion_obj(symbol_iloc, sym->iloc);
342 iloc->loc.location = sym_ptr->import_location.location;
343 iloc->name = zend_string_from_ion(&sym_ptr->import_location.name);
344
345 php_ion_symbol_iloc_ctor(iloc);
346 }
347
348 php_ion_symbol_ctor(sym);
349 }
350
351 typedef struct php_ion_symbol_table {
352 zend_object std;
353 } php_ion_symbol_table;
354
355 typedef struct php_ion_decimal_ctx {
356 decContext ctx;
357 zend_object std;
358 } php_ion_decimal_ctx;
359
360 static inline void php_ion_decimal_ctx_ctor(php_ion_decimal_ctx *obj) {
361 zval tmp, *zbits = zend_read_property(obj->std.ce, &obj->std, ZEND_STRL("bits"), 1, &tmp);
362
363 int bits = 128;
364 if (zbits != &EG(uninitialized_zval)) {
365 bits = Z_LVAL_P(zbits);
366 } else {
367 zend_update_property_long(obj->std.ce, &obj->std, ZEND_STRL("bits"), bits);
368 }
369 switch (bits) {
370 case 32:
371 case 64:
372 case 128:
373 decContextDefault(&obj->ctx, bits);
374 break;
375 default:
376 zend_throw_exception_ex(spl_ce_InvalidArgumentException, IERR_INVALID_ARG,
377 "Decimal context only allows 32, 64 or 128 bits");
378 }
379
380 }
381
382 typedef struct php_ion_decimal {
383 ION_DECIMAL dec;
384 zend_object *ctx, std;
385 } php_ion_decimal;
386
387 static inline zend_string *php_ion_decimal_to_string(ION_DECIMAL *dec)
388 {
389 zend_string *zstr = zend_string_alloc(ION_DECIMAL_STRLEN(dec), 0);
390 (void) ion_decimal_to_string(dec, zstr->val);
391 return zend_string_truncate(zstr, strlen(zstr->val), 0);
392 }
393
394 static inline void php_ion_decimal_to_int(ION_DECIMAL *dec, decContext *ctx, zend_long *l)
395 {
396 ION_INT *ii = NULL;
397 ION_CHECK(ion_int_alloc(NULL, &ii));
398 ION_CHECK(ion_decimal_to_ion_int(dec, ctx, ii), ion_int_free(ii));
399 int64_t i64;
400 ION_CHECK(ion_int_to_int64(ii, &i64), ion_int_free(ii));
401 *l = i64;
402 ion_int_free(ii);
403 }
404
405 static inline void php_ion_decimal_ctor(php_ion_decimal *obj)
406 {
407 if (!obj->ctx) {
408 zval zdc;
409 object_init_ex(&zdc, ce_Decimal_Context);
410 obj->ctx = Z_OBJ(zdc);
411 php_ion_decimal_ctx_ctor(php_ion_obj(decimal_ctx, obj->ctx));
412 }
413 update_property_obj(&obj->std, ZEND_STRL("context"), obj->ctx);
414
415 if (ion_decimal_is_integer(&obj->dec)) {
416 zend_long l;
417 php_ion_decimal_to_int(&obj->dec, &php_ion_obj(decimal_ctx, obj->ctx)->ctx, &l);
418 zend_update_property_long(obj->std.ce, &obj->std, ZEND_STRL("number"), l);
419 } else {
420 zend_string *zstr = php_ion_decimal_to_string(&obj->dec);
421 zend_update_property_str(obj->std.ce, &obj->std, ZEND_STRL("number"), zstr);
422 zend_string_release(zstr);
423 }
424 }
425
426 static inline void php_ion_decimal_dtor(php_ion_decimal *obj)
427 {
428 ion_decimal_free(&obj->dec);
429 }
430
431 typedef php_date_obj php_ion_timestamp;
432
433 static inline void php_ion_timestamp_ctor(php_ion_timestamp *obj, zend_long precision, zend_string *fmt, zend_string *dt, zval *tz)
434 {
435 if (!obj->time) {
436 php_date_initialize(obj, dt ? dt->val : "now", dt ? dt->len : 3, fmt ? fmt->val : NULL, tz, PHP_DATE_INIT_CTOR);
437 }
438 zend_update_property_long(obj->std.ce, &obj->std, ZEND_STRL("precision"), precision);
439 if (fmt) {
440 zend_update_property_str(obj->std.ce, &obj->std, ZEND_STRL("format"), fmt);
441 } else {
442 zend_update_property_stringl(obj->std.ce, &obj->std, ZEND_STRL("format"), ZEND_STRL("c"));
443 }
444 }
445
446 static inline void php_ion_timestamp_dtor(php_ion_timestamp *obj)
447 {
448 if (obj->time) {
449 timelib_time_dtor(obj->time);
450 }
451 }
452
453 static inline zend_long php_usec_from_ion(const decQuad *frac, decContext *ctx)
454 {
455 decQuad microsecs, result;
456 decQuadMultiply(&result, decQuadFromInt32(&microsecs, 1000000), frac, ctx);
457 return (zend_long) decQuadToUInt32(&result, ctx, DEC_ROUND_HALF_EVEN);
458 }
459
460 static inline decQuad *ion_ts_frac_from_usec(decQuad *frac, zend_long usec, decContext *ctx)
461 {
462 decQuad microsecs, us;
463 return decQuadDivide(frac, decQuadFromInt32(&us, usec), decQuadFromInt32(&microsecs, 1000000), ctx);
464 }
465
466 static inline timelib_time* php_time_from_ion(const ION_TIMESTAMP *ts, decContext *ctx, zend_string **fmt)
467 {
468 timelib_time *time = timelib_time_ctor();
469
470 switch (ts->precision) {
471 case ION_TS_FRAC:
472 time->us = php_usec_from_ion(&ts->fraction, ctx);
473 if (fmt) *fmt = zend_string_init(ZEND_STRL("c"), 0);
474 /* fallthrough */
475 case ION_TS_SEC:
476 time->s = ts->seconds;
477 if (fmt && !*fmt) *fmt = zend_string_init(ZEND_STRL("Y-m-d\\TH:i:sP"), 0);
478 /* fallthrough */
479 case ION_TS_MIN:
480 time->i = ts->minutes;
481 time->h = ts->hours;
482 if (fmt && !*fmt) *fmt = zend_string_init(ZEND_STRL("Y-m-d\\TH:iP"), 0);
483 /* fallthrough */
484 case ION_TS_DAY:
485 time->d = ts->day;
486 if (fmt && !*fmt) *fmt = zend_string_init(ZEND_STRL("Y-m-d\\T"), 0);
487 /* fallthrough */
488 case ION_TS_MONTH:
489 time->m = ts->month;
490 if (fmt && !*fmt) *fmt = zend_string_init(ZEND_STRL("Y-m\\T"), 0);
491 /* fallthrough */
492 case ION_TS_YEAR:
493 time->y = ts->year;
494 if (fmt && !*fmt) *fmt = zend_string_init(ZEND_STRL("Y\\T"), 0);
495 /* fallthrough */
496 default:
497 if (fmt && !*fmt) *fmt = zend_string_init(ZEND_STRL("c"), 0);
498 time->z = ts->tz_offset * 60;
499 }
500
501 return time;
502 }
503
504 static inline ION_TIMESTAMP *ion_timestamp_from_php(ION_TIMESTAMP *buf, php_ion_timestamp *ts, decContext *ctx)
505 {
506 memset(buf, 0, sizeof(*buf));
507
508 zval tmp;
509 uint8_t precision = Z_LVAL_P(zend_read_property(ts->std.ce, &ts->std, ZEND_STRL("precision"), 0, &tmp));
510
511 if (!precision || precision > ION_TS_FRAC) {
512 zend_throw_exception_ex(spl_ce_InvalidArgumentException, IERR_INVALID_ARG,
513 "Invalid precision (%u) of ion\\Timestamp", (unsigned) precision);
514 } else switch ((buf->precision = precision)) {
515 case ION_TS_FRAC:
516 ion_ts_frac_from_usec(&buf->fraction, ts->time->us, ctx);
517 /* fallthrough */
518 case ION_TS_SEC:
519 buf->seconds = ts->time->s;
520 /* fallthrough */
521 case ION_TS_MIN:
522 buf->minutes = ts->time->i;
523 /* fallthrough */
524 case ION_TS_DAY:
525 buf->hours = ts->time->h;
526 buf->day = ts->time->d;
527 /* fallthrough */
528 case ION_TS_MONTH:
529 buf->month = ts->time->m;
530 /* fallthrough */
531 case ION_TS_YEAR:
532 buf->year = ts->time->y;
533 /* fallthrough */
534 default:
535 buf->tz_offset = ts->time->z / 60;
536 }
537
538 return buf;
539 }
540
541 typedef struct php_ion_catalog {
542 ION_CATALOG *cat;
543 zend_object std;
544 } php_ion_catalog;
545
546 typedef struct php_ion_reader_options {
547 ION_READER_OPTIONS opt;
548 zend_object *cat, *dec_ctx, *cb, std;
549 } php_ion_reader_options;
550
551 typedef struct php_ion_reader {
552 ION_READER *reader;
553 ION_TYPE state;
554 enum {
555 BUFFER_READER,
556 STREAM_READER,
557 } type;
558 union {
559 zend_string *buffer;
560 struct {
561 php_stream *ptr;
562 ION_STRING buf;
563 } stream;
564 };
565 struct {
566 zend_bool call_magic_unserialize;
567 zend_string *custom_unserialize;
568 } php;
569 zend_object *opt, std;
570 } php_ion_reader;
571
572 static iERR php_ion_reader_stream_handler(struct _ion_user_stream *user)
573 {
574 php_ion_reader *reader = (php_ion_reader *) user->handler_state;
575 size_t remaining = 0, spare = reader->stream.buf.length;
576
577 if (user->curr && user->limit && (remaining = user->limit - user->curr)) {
578 memmove(reader->stream.buf.value, user->curr, remaining);
579 user->limit -= remaining;
580 spare -= remaining;
581 } else {
582 user->curr = user->limit = reader->stream.buf.value;
583 }
584
585 ssize_t read = php_stream_read(reader->stream.ptr, (char *) user->limit, spare);
586 if (EXPECTED(read > 0)) {
587 user->limit += read;
588 return IERR_OK;
589 }
590
591 if (EXPECTED(read == 0)) {
592 return IERR_EOF;
593 }
594
595 return IERR_READ_ERROR;
596 }
597
598 static iERR on_context_change(void *context, ION_COLLECTION *imports)
599 {
600 if (context) {
601 php_ion_reader *obj = php_ion_obj(reader, context);
602 (void) obj;
603 }
604 return IERR_OK;
605 }
606
607 static ION_READER_CONTEXT_CHANGE_NOTIFIER EMPTY_READER_CHANGE_NOTIFIER = {
608 on_context_change,
609 NULL
610 };
611
612 static inline void php_ion_reader_ctor(php_ion_reader *obj)
613 {
614 iERR err;
615 php_ion_reader_options *opt = php_ion_obj(reader_options, obj->opt);
616
617 if (opt) {
618 opt->opt.context_change_notifier.context = obj;
619 }
620 if (obj->type == STREAM_READER) {
621 PTR_CHECK(obj->stream.ptr);
622 GC_ADDREF(obj->stream.ptr->res);
623
624 php_ion_reader_options *opt = php_ion_obj(reader_options, obj->opt);
625 obj->stream.buf.length = opt ? opt->opt.allocation_page_size : 0x1000;
626 obj->stream.buf.value = emalloc(obj->stream.buf.length);
627 err = ion_reader_open_stream(&obj->reader, obj, php_ion_reader_stream_handler, opt ? &opt->opt : NULL);
628
629 } else {
630 err = ion_reader_open_buffer(&obj->reader,
631 (BYTE *) obj->buffer->val, obj->buffer->len,
632 opt ? &opt->opt : NULL);
633 }
634 if (opt) {
635 opt->opt.context_change_notifier.context = NULL;
636 }
637
638 ION_CHECK(err);
639 OBJ_CHECK(obj);
640 }
641 static inline void php_ion_reader_dtor(php_ion_reader *obj)
642 {
643 if (obj->reader) {
644 ion_reader_close(obj->reader);
645 }
646 if (obj->type == STREAM_READER) {
647 if (obj->stream.buf.value) {
648 efree(obj->stream.buf.value);
649 }
650 if (obj->stream.ptr) {
651 zend_list_delete(obj->stream.ptr->res);
652 }
653 } else {
654 if (obj->buffer) {
655 zend_string_release(obj->buffer);
656 }
657 }
658 }
659
660 typedef struct php_ion_writer_options {
661 ION_WRITER_OPTIONS opt;
662 zend_object *cat, *dec_ctx, *col, std;
663 } php_ion_writer_options;
664
665 typedef struct php_ion_writer {
666 ION_WRITER *writer;
667 enum {
668 BUFFER_WRITER,
669 STREAM_WRITER,
670 } type;
671 union {
672 struct {
673 zval val;
674 smart_str str;
675 } buffer;
676 struct {
677 ION_STRING buf;
678 php_stream *ptr;
679 } stream;
680 };
681 struct {
682 zend_bool call_magic_serialize;
683 zend_string *custom_serialize;
684 } php;
685 zend_object *opt, std;
686
687 } php_ion_writer;
688
689 static iERR php_ion_writer_stream_handler(struct _ion_user_stream *user)
690 {
691 php_ion_writer *writer = (php_ion_writer *) user->handler_state;
692
693 if (EXPECTED(user->limit && user->curr)) {
694 ptrdiff_t len = user->curr - writer->stream.buf.value;
695 if (len != php_stream_write(writer->stream.ptr, (char *) writer->stream.buf.value, len)) {
696 return IERR_WRITE_ERROR;
697 }
698 }
699 user->curr = writer->stream.buf.value;
700 user->limit = writer->stream.buf.value + writer->stream.buf.length;
701 return IERR_OK;
702 }
703
704 #define REF_STR() do { \
705 ZVAL_NEW_STR(ref, obj->buffer.str.s); \
706 GC_ADDREF(obj->buffer.str.s); \
707 } while (0)
708
709 #define NEW_REF_STR() do {\
710 if (Z_STR_P(ref) != obj->buffer.str.s) { \
711 zval_ptr_dtor(ref); \
712 REF_STR(); \
713 } \
714 } while(0)
715
716 static inline void php_ion_writer_stream_init(php_ion_writer *obj, php_ion_writer_options *opt)
717 {
718 PTR_CHECK(obj->stream.ptr);
719 GC_ADDREF(obj->stream.ptr->res);
720
721 obj->stream.buf.length = opt ? opt->opt.allocation_page_size : 0x1000;
722 obj->stream.buf.value = emalloc(obj->stream.buf.length);
723 }
724 static inline void php_ion_writer_buffer_init(php_ion_writer *obj)
725 {
726 zval *ref = &obj->buffer.val;
727 ZVAL_DEREF(ref);
728
729 smart_str_alloc(&obj->buffer.str, 0, 0);
730 smart_str_0(&obj->buffer.str);
731 REF_STR();
732 }
733
734 static inline void php_ion_writer_buffer_grow(php_ion_writer *obj)
735 {
736 zval *ref = &obj->buffer.val;
737 ZVAL_DEREF(ref);
738
739 switch (GC_REFCOUNT(obj->buffer.str.s)) {
740 case 2:
741 // nothing to do
742 break;
743 case 1:
744 // we've been separated
745 GC_ADDREF(obj->buffer.str.s);
746 break;
747 default:
748 // we have to separate
749 fprintf(stderr, "SEPARATE\n");
750 obj->buffer.str.s = zend_string_dup(obj->buffer.str.s, 0);
751 break;
752 }
753
754 zend_string *old = obj->buffer.str.s;
755 GC_DELREF(old);
756 smart_str_erealloc(&obj->buffer.str, obj->buffer.str.a << 1);
757 if (old == obj->buffer.str.s) {
758 GC_ADDREF(old);
759 } else if(old == Z_STR_P(ref)) {
760 ZVAL_NULL(ref);
761 }
762
763 NEW_REF_STR();
764 }
765
766 static iERR php_ion_writer_buffer_handler(struct _ion_user_stream *user)
767 {
768 php_ion_writer *writer = (php_ion_writer *) user->handler_state;
769
770 if (user->curr) {
771 writer->buffer.str.s->len += user->curr - (BYTE *) &writer->buffer.str.s->val[writer->buffer.str.s->len];
772 smart_str_0(&writer->buffer.str);
773 if (user->limit == user->curr) {
774 php_ion_writer_buffer_grow(writer);
775 }
776 }
777 user->curr = (BYTE *) &writer->buffer.str.s->val[writer->buffer.str.s->len];
778 user->limit = user->curr + writer->buffer.str.a - writer->buffer.str.s->len;
779
780 return IERR_OK;
781 }
782
783 static inline void php_ion_writer_ctor(php_ion_writer *obj)
784 {
785 if (obj->opt) {
786 update_property_obj(&obj->std, ZEND_STRL("options"), obj->opt);
787 }
788
789 php_ion_writer_options *opt = php_ion_obj(writer_options, obj->opt);
790 ION_STREAM_HANDLER h;
791 if (obj->type == STREAM_WRITER) {
792 h = php_ion_writer_stream_handler;
793 php_ion_writer_stream_init(obj, opt);
794 } else {
795 h = php_ion_writer_buffer_handler;
796 php_ion_writer_buffer_init(obj);
797 }
798
799 ION_CHECK(ion_writer_open_stream(&obj->writer, h, obj, opt ? &opt->opt : NULL));
800 OBJ_CHECK(obj);
801 }
802
803 static inline void php_ion_writer_dtor(php_ion_writer *obj)
804 {
805 if (obj->writer) {
806 ion_writer_close(obj->writer);
807 }
808 if (obj->type == STREAM_WRITER) {
809 if (obj->stream.buf.value) {
810 efree(obj->stream.buf.value);
811 }
812 if (obj->stream.ptr) {
813 zend_list_delete(obj->stream.ptr->res);
814 }
815 } else {
816 smart_str_0(&obj->buffer.str);
817 zend_string_release(obj->buffer.str.s);
818 zval_ptr_dtor(&obj->buffer.val);
819 }
820 if (obj->php.custom_serialize) {
821 zend_string_release(obj->php.custom_serialize);
822 }
823 }
824
825 static inline void php_ion_serialize_zval(php_ion_writer *, zval *);
826
827 static inline void php_ion_serialize_struct(php_ion_writer *obj, zend_array *arr)
828 {
829 ION_CHECK(ion_writer_start_container(obj->writer, tid_STRUCT));
830
831 zval *v;
832 zend_ulong h;
833 zend_string *k = NULL;
834 if (arr) ZEND_HASH_FOREACH_KEY_VAL_IND(arr, h, k, v)
835 ION_STRING is;
836 if (k) {
837 ION_CHECK(ion_writer_write_field_name(obj->writer, ion_string_from_zend(&is, k)));
838 } else {
839 char buf[MAX_LENGTH_OF_LONG + 1], *end = buf + sizeof(buf) - 1;
840 char *ptr = zend_print_long_to_buf(end, (zend_long) h);
841 ION_CHECK(ion_writer_write_field_name(obj->writer, ion_string_from_cstr(&is, ptr, end - ptr)));
842 }
843
844 php_ion_serialize_zval(obj, v);
845 ION_CATCH();
846 ZEND_HASH_FOREACH_END();
847
848 ION_CHECK(ion_writer_finish_container(obj->writer));
849 }
850
851 static inline void php_ion_serialize_list(php_ion_writer *obj, zend_array *arr)
852 {
853 ION_CHECK(ion_writer_start_container(obj->writer, tid_LIST));
854
855 zval *v;
856 ZEND_HASH_FOREACH_VAL_IND(arr, v)
857 php_ion_serialize_zval(obj, v);
858 ION_CATCH();
859 ZEND_HASH_FOREACH_END();
860
861 ION_CHECK(ion_writer_finish_container(obj->writer));
862 }
863
864 static inline void php_ion_serialize_array(php_ion_writer *obj, zend_array *arr)
865 {
866 if (zend_array_is_list(arr)) {
867 php_ion_serialize_list(obj, arr);
868 } else {
869 php_ion_serialize_struct(obj, arr);
870 }
871 }
872
873 static inline void php_ion_serialize_object_iface(php_ion_writer *obj, zend_object *zobject)
874 {
875 uint8_t *buf;
876 size_t len;
877 zval tmp;
878
879 ZVAL_OBJ(&tmp, zobject);
880 if (SUCCESS == zobject->ce->serialize(&tmp, &buf, &len, NULL)) {
881 ION_STRING is;
882 ION_CHECK(ion_writer_add_annotation(obj->writer, ion_string_from_cstr(&is, ZEND_STRL("S"))));
883 ION_CHECK(ion_writer_add_annotation(obj->writer, ion_string_from_zend(&is, zobject->ce->name)));
884 ION_CHECK(ion_writer_write_string(obj->writer, ion_string_from_cstr(&is, (char *) buf, len)));
885 efree(buf);
886 } else if (!EG(exception)){
887 zend_throw_exception_ex(spl_ce_UnexpectedValueException, IERR_INTERNAL_ERROR,
888 "Failed to serialize class %s", zobject->ce->name->val);
889 }
890 }
891
892 static inline void php_ion_serialize_object_magic(php_ion_writer *obj, zend_object *zobject, zend_function *fn)
893 {
894 zval rv;
895
896 ZVAL_NULL(&rv);
897 zend_call_method_with_0_params(zobject, zobject->ce, fn ? &fn : &zobject->ce->__serialize, "", &rv);
898 ION_CATCH();
899
900 if (IS_ARRAY == Z_TYPE(rv)) {
901 ION_STRING is;
902 ION_CHECK(ion_writer_add_annotation(obj->writer, ion_string_from_cstr(&is, fn ? "C" : "O", 1)));
903 ION_CHECK(ion_writer_add_annotation(obj->writer, ion_string_from_zend(&is, zobject->ce->name)));
904 php_ion_serialize_zval(obj, &rv);
905 zval_ptr_dtor(&rv);
906 } else {
907 zend_throw_exception_ex(spl_ce_UnexpectedValueException, IERR_INTERNAL_ERROR,
908 "%s serializer %s::%s did not return an array",
909 fn ? "Custom" : "Magic", zobject->ce->name->val,
910 fn ? fn->common.function_name->val : "__serialize");
911 }
912 }
913
914 static inline zend_string *fq_enum_case(zend_object *zobject)
915 {
916 zval *cn = zend_enum_fetch_case_name(zobject);
917 zend_string *en = zend_string_alloc(zobject->ce->name->len + Z_STRLEN_P(cn) + strlen("\\"), 0);
918 memcpy(en->val, zobject->ce->name->val, zobject->ce->name->len);
919 en->val[zobject->ce->name->len] = '\\';
920 memcpy(&en->val[zobject->ce->name->len + 1], Z_STRVAL_P(cn), Z_STRLEN_P(cn));
921 en->val[en->len] = 0;
922 return en;
923 }
924
925 static inline void php_ion_serialize_object_enum(php_ion_writer *obj, zend_object *zobject)
926 {
927 ION_STRING is;
928 ION_CHECK(ion_writer_add_annotation(obj->writer, ion_string_from_cstr(&is, ZEND_STRL("E"))));
929
930 zend_string *en = fq_enum_case(zobject);
931 ION_CHECK(ion_writer_write_string(obj->writer, ion_string_from_zend(&is, en)));
932 zend_string_release(en);
933 }
934
935 static inline void php_ion_serialize_object_std(php_ion_writer *obj, zend_object *zobject)
936 {
937 ION_STRING is;
938
939 if (zobject->ce != zend_standard_class_def) {
940 ION_CHECK(ion_writer_add_annotation(obj->writer, ion_string_from_cstr(&is, ZEND_STRL("c"))));
941 ION_CHECK(ion_writer_add_annotation(obj->writer, ion_string_from_zend(&is, zobject->ce->name)));
942 } else {
943 ION_CHECK(ion_writer_add_annotation(obj->writer, ion_string_from_cstr(&is, ZEND_STRL("o"))));
944 }
945
946 zval zobj;
947 ZVAL_OBJ(&zobj, zobject);
948 HashTable *props = zend_get_properties_for(&zobj, ZEND_PROP_PURPOSE_SERIALIZE);
949 if (props) {
950 php_ion_serialize_struct(obj, props);
951 zend_release_properties(props);
952 } else {
953 zend_throw_exception_ex(spl_ce_UnexpectedValueException, IERR_INTERNAL_ERROR,
954 "Could not get properties for serialization of class %s",
955 zobject->ce->name->val);
956 }
957 }
958
959 static inline bool can_call_magic_serialize(php_ion_writer *obj, zend_class_entry *ce)
960 {
961 if (ce->__serialize && obj->php.call_magic_serialize) {
962 return true;
963 }
964 return false;
965 }
966
967 static inline bool can_call_iface_serialize(php_ion_writer *obj, zend_class_entry *ce)
968 {
969 return !!ce->serialize;
970 }
971
972 static inline bool can_call_custom_serialize(php_ion_writer *obj, zend_object *zobject, zend_function **fn)
973 {
974 if (obj->php.custom_serialize) {
975 return !!((*fn = zend_hash_find_ptr(&zobject->ce->function_table, obj->php.custom_serialize)));
976 }
977 return false;
978 }
979
980 static inline void php_ion_serialize_object(php_ion_writer *obj, zend_object *zobject)
981 {
982 zend_function *fn;
983 zend_class_entry *ce = zobject->ce;
984 ZEND_ASSERT(ce);
985
986 if (ce->ce_flags & ZEND_ACC_NOT_SERIALIZABLE) {
987 zend_throw_exception_ex(spl_ce_InvalidArgumentException, IERR_INVALID_ARG,
988 "Serializing %s is not allowed", ce->name->val);
989 return;
990 }
991
992 if (can_call_magic_serialize(obj, ce)) {
993 php_ion_serialize_object_magic(obj, zobject, NULL);
994 } else if (can_call_iface_serialize(obj, ce)) {
995 php_ion_serialize_object_iface(obj, zobject);
996 } else if (can_call_custom_serialize(obj, zobject, &fn)) {
997 php_ion_serialize_object_magic(obj, zobject, fn);
998 } else if (zobject->ce->ce_flags & ZEND_ACC_ENUM) {
999 php_ion_serialize_object_enum(obj, zobject);
1000 } else if (ce == ce_Symbol) {
1001 ION_CHECK(ion_writer_write_ion_symbol(obj->writer, &php_ion_obj(symbol, zobject)->sym));
1002 } else if (ce == ce_Decimal) {
1003 ION_CHECK(ion_writer_write_ion_decimal(obj->writer, &php_ion_obj(decimal, zobject)->dec));
1004 } else if (ce == ce_Timestamp) {
1005 ION_TIMESTAMP its;
1006 php_ion_timestamp *pts = php_ion_obj(timestamp, zobject);
1007 php_ion_writer_options *opt = php_ion_obj(writer_options, obj->opt);
1008 decContext *ctx = opt ? opt->opt.decimal_context : NULL;
1009 ION_CHECK(ion_writer_write_timestamp(obj->writer, ion_timestamp_from_php(&its, pts, ctx)));
1010 } else {
1011 php_ion_serialize_object_std(obj, zobject);
1012 }
1013 }
1014
1015 static inline void php_ion_serialize_refcounted(php_ion_writer *obj, zval *zv)
1016 {
1017 zend_ulong idx = (zend_ulong) (uintptr_t) Z_COUNTED_P(zv);
1018
1019 ION_STRING is;
1020 if (zend_hash_index_exists(&php_ion_globals.serializer.ids, idx)) {
1021 zval *num = zend_hash_index_find(&php_ion_globals.serializer.ids, idx);
1022
1023 ION_CHECK(ion_writer_add_annotation(obj->writer, ion_string_from_cstr(&is, ZEND_STRL("r"))));
1024 ION_CHECK(ion_writer_write_int64(obj->writer, Z_LVAL_P(num)));
1025 } else {
1026 zval num;
1027
1028 ZVAL_LONG(&num, zend_hash_num_elements(&php_ion_globals.serializer.ids));
1029 zend_hash_index_add(&php_ion_globals.serializer.ids, idx, &num);
1030
1031 Z_TRY_ADDREF_P(zv);
1032 zend_hash_next_index_insert(&php_ion_globals.serializer.tmp, zv);
1033
1034 switch (Z_TYPE_P(zv)) {
1035 case IS_STRING:
1036 ION_CHECK(ion_writer_write_string(obj->writer, ion_string_from_zend(&is, Z_STR_P(zv))));
1037 break;
1038
1039 case IS_ARRAY:
1040 php_ion_serialize_array(obj, Z_ARRVAL_P(zv));
1041 break;
1042
1043 case IS_OBJECT:
1044 php_ion_serialize_object(obj, Z_OBJ_P(zv));
1045 break;
1046
1047 case IS_REFERENCE:
1048 ION_CHECK(ion_writer_add_annotation(obj->writer, ion_string_from_cstr(&is, ZEND_STRL("R"))));
1049 php_ion_serialize_zval(obj, Z_REFVAL_P(zv));
1050 break;
1051 }
1052 }
1053 }
1054
1055 static inline void php_ion_serialize_zval(php_ion_writer *obj, zval *zv)
1056 {
1057 OBJ_CHECK(obj);
1058
1059 switch (Z_TYPE_P(zv)) {
1060 case IS_NULL:
1061 ION_CHECK(ion_writer_write_null(obj->writer));
1062 break;
1063 case IS_TRUE:
1064 ION_CHECK(ion_writer_write_bool(obj->writer, TRUE));
1065 break;
1066 case IS_FALSE:
1067 ION_CHECK(ion_writer_write_bool(obj->writer, FALSE));
1068 break;
1069 case IS_LONG:
1070 ION_CHECK(ion_writer_write_int64(obj->writer, Z_LVAL_P(zv)));
1071 break;
1072 case IS_DOUBLE:
1073 ION_CHECK(ion_writer_write_double(obj->writer, Z_DVAL_P(zv)));
1074 break;
1075 case IS_STRING:
1076 case IS_ARRAY:
1077 case IS_OBJECT:
1078 case IS_REFERENCE:
1079 php_ion_serialize_refcounted(obj, zv);
1080 break;
1081 default:
1082 zend_throw_exception_ex(spl_ce_InvalidArgumentException, IERR_INVALID_ARG,
1083 "Failed to serialize value of type %s", zend_zval_get_legacy_type(zv)->val);
1084 }
1085 }
1086
1087 void php_ion_serialize(php_ion_writer *obj, zval *zv, zval *return_value)
1088 {
1089 zval zwriter;
1090
1091 if (obj) {
1092 ZVAL_OBJ_COPY(&zwriter, &obj->std);
1093 } else {
1094 object_init_ex(&zwriter, ce_Writer_Buffer_Writer);
1095 obj = php_ion_obj(writer, Z_OBJ(zwriter));
1096 obj->type = BUFFER_WRITER;
1097 php_ion_writer_ctor(obj);
1098 }
1099
1100 php_ion_globals_serializer_step();
1101
1102 /* start off with a global PHP annotation instead of repeating it all over the place */
1103 ION_STRING is;
1104 ION_CHECK(ion_writer_add_annotation(obj->writer, ion_string_from_cstr(&is, ZEND_STRL("PHP"))));
1105
1106 php_ion_serialize_zval(obj, zv);
1107
1108 /* make sure to flush when done, else str.s might not contain everything until the writer is closed */
1109 ion_writer_flush(obj->writer, NULL);
1110 RETVAL_STR_COPY(obj->buffer.str.s);
1111
1112 php_ion_globals_serializer_exit();
1113
1114 zval_ptr_dtor(&zwriter);
1115 }
1116
1117 static inline void php_ion_unserialize_zval(php_ion_reader *obj, zval *return_value, ION_TYPE *typ);
1118
1119 static inline bool can_call_magic_unserialize(php_ion_reader *obj, zend_class_entry *ce)
1120 {
1121 if (ce->__unserialize && obj->php.call_magic_unserialize) {
1122 return true;
1123 }
1124 return false;
1125 }
1126
1127 static inline bool can_call_iface_unserialize(php_ion_reader *obj, zend_class_entry *ce)
1128 {
1129 return !!ce->unserialize;
1130 }
1131
1132 static inline bool can_call_custom_unserialize(php_ion_reader *obj, zend_object *zobject, zend_function **fn)
1133 {
1134 if (obj->php.custom_unserialize) {
1135 return !!((*fn = zend_hash_find_ptr(&zobject->ce->function_table, obj->php.custom_unserialize)));
1136 }
1137 return false;
1138 }
1139
1140 static inline zval *php_ion_unserialize_class(php_ion_reader *obj, zend_string *class_name, zval *return_value)
1141 {
1142 zend_class_entry *ce = zend_lookup_class(class_name);
1143
1144 if (ce) {
1145 object_init_ex(return_value, ce);
1146 return zend_hash_next_index_insert(&php_ion_globals.unserializer.ids, return_value);
1147 }
1148
1149 zend_throw_exception_ex(spl_ce_RuntimeException, IERR_IMPORT_NOT_FOUND,
1150 "Could not find class %s", class_name->val);
1151 return NULL;
1152 }
1153
1154 static inline void php_ion_unserialize_object_iface(php_ion_reader *obj, zend_string *class_name, zval *return_value)
1155 {
1156 ZEND_ASSERT(Z_TYPE_P(return_value) == IS_STRING);
1157 zend_string *s = Z_STR_P(return_value);
1158
1159 zval *backref = php_ion_unserialize_class(obj, class_name, return_value);
1160 ION_CATCH();
1161
1162 zend_class_entry *ce = Z_OBJCE_P(return_value);
1163 if (can_call_iface_unserialize(obj, ce)) {
1164 if (SUCCESS == ce->unserialize(backref, ce, (BYTE *) s->val, s->len, NULL)) {
1165 // remove all this Serializable crap in PHP-9
1166 zval_ptr_dtor(return_value);
1167 ZVAL_COPY_VALUE(return_value, backref);
1168 } else if (!EG(exception)) {
1169 zend_throw_exception_ex(spl_ce_UnexpectedValueException, IERR_INTERNAL_ERROR,
1170 "Failed to unserialize class %s", ce->name->val);
1171 }
1172 } else {
1173 zend_throw_exception_ex(spl_ce_RuntimeException, IERR_INVALID_TOKEN,
1174 "Class %s does not implement Serializable", class_name->val);
1175 }
1176
1177 zend_string_release(s);
1178 }
1179
1180 static inline void php_ion_unserialize_hash(php_ion_reader *obj, zval *return_value)
1181 {
1182 zend_hash_next_index_insert(&php_ion_globals.unserializer.ids, return_value);
1183
1184 ION_CHECK(ion_reader_step_in(obj->reader));
1185
1186 while (true) {
1187 ION_TYPE typ;
1188 ION_CHECK(ion_reader_next(obj->reader, &typ));
1189
1190 ION_STRING name;
1191 ION_CHECK(ion_reader_get_field_name(obj->reader, &name));
1192 zend_string *key = zend_string_from_ion(&name);
1193
1194 zval zvalue;
1195 php_ion_unserialize_zval(obj, &zvalue, &typ);
1196 ION_CATCH(zend_string_release(key));
1197
1198 if (typ == tid_EOF) {
1199 zend_string_release(key);
1200 break;
1201 }
1202
1203 zend_symtable_update(HASH_OF(return_value), key, &zvalue);
1204 zend_string_release(key);
1205 }
1206
1207 ION_CHECK(ion_reader_step_out(obj->reader));
1208 }
1209
1210 static inline void verify_unserializer(php_ion_reader *obj, uint8_t object_type,
1211 zend_string *class_name, zend_object *zobject, zend_function **fn)
1212 {
1213 switch (object_type) {
1214 case 'C':
1215 if (!can_call_custom_unserialize(obj, zobject, fn)) {
1216 zend_throw_exception_ex(spl_ce_RuntimeException, IERR_INVALID_TOKEN,
1217 "Could not find custom serializer method of %s", class_name->val);
1218 }
1219 break;
1220
1221 case 'O':
1222 if (!can_call_magic_unserialize(obj, zobject->ce)) {
1223 zend_throw_exception_ex(spl_ce_RuntimeException, IERR_INVALID_TOKEN,
1224 "Could not find method %s::__serialize()", class_name->val);
1225 }
1226 *fn = zobject->ce->__unserialize;
1227 break;
1228
1229 default:
1230 zend_throw_exception_ex(spl_ce_RuntimeException, IERR_INVALID_TOKEN,
1231 "Invalid object type %c", object_type);
1232 }
1233 }
1234 static inline void php_ion_unserialize_object(php_ion_reader *obj, uint8_t object_type, zend_string *class_name, zval *return_value)
1235 {
1236 // backup possible backref to array returned by magic/custom __serialize()
1237 zval zarr;
1238 ZVAL_COPY_VALUE(&zarr, return_value);
1239 zend_hash_next_index_insert(&php_ion_globals.unserializer.tmp, &zarr);
1240
1241 php_ion_unserialize_class(obj, class_name, return_value);
1242 ION_CATCH();
1243
1244 zend_object *zobject = Z_OBJ_P(return_value);
1245 zend_function *fn = NULL;
1246 verify_unserializer(obj, object_type, class_name, zobject, &fn);
1247 ION_CATCH();
1248
1249 if (Z_TYPE(zarr) != IS_ARRAY) {
1250 ZEND_ASSERT(Z_TYPE(zarr) != IS_OBJECT);
1251 array_init(&zarr);
1252 zend_hash_next_index_insert(&php_ion_globals.unserializer.tmp, &zarr);
1253 php_ion_unserialize_hash(obj, &zarr);
1254 ION_CATCH();
1255 }
1256
1257 zval rv;
1258 ZVAL_NULL(&rv);
1259 zend_call_method_with_1_params(zobject, zobject->ce, &fn, "", &rv, &zarr);
1260 zval_ptr_dtor(&rv);
1261 }
1262
1263 static inline void php_ion_unserialize_struct(php_ion_reader *obj, uint8_t object_type, zend_string *class_name, zval *return_value)
1264 {
1265 if (class_name) {
1266 php_ion_unserialize_object(obj, object_type, class_name, return_value);
1267 } else if (!object_type) {
1268 array_init(return_value);
1269 php_ion_unserialize_hash(obj, return_value);
1270 } else if (object_type == 'o') {
1271 object_init(return_value);
1272 php_ion_unserialize_hash(obj, return_value);
1273 } else {
1274 zend_throw_exception_ex(spl_ce_RuntimeException, IERR_INVALID_TOKEN,
1275 "Invalid object type %c", object_type);
1276 }
1277 }
1278
1279 static inline void php_ion_unserialize_list(php_ion_reader *obj, zval *return_value)
1280 {
1281 ION_CHECK(ion_reader_step_in(obj->reader));
1282 array_init(return_value);
1283 zend_hash_next_index_insert(&php_ion_globals.unserializer.ids, return_value);
1284
1285 while (true) {
1286 ION_TYPE typ;
1287 ION_CHECK(ion_reader_next(obj->reader, &typ));
1288
1289 zval next;
1290 php_ion_unserialize_zval(obj, &next, &typ);
1291 ION_CATCH();
1292
1293 if (typ == tid_EOF) {
1294 break;
1295 }
1296
1297 zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &next);
1298 }
1299
1300 ION_CHECK(ion_reader_step_out(obj->reader));
1301 }
1302
1303 static inline void php_ion_unserialize_lob(php_ion_reader *obj, zval *return_value)
1304 {
1305 zend_string *zstr = zend_string_alloc(0x1000, 0);
1306 again:
1307 SIZE read = 0;
1308 iERR err = ion_reader_read_lob_bytes(obj->reader, (BYTE *) zstr->val, zstr->len, &read);
1309 if (err == IERR_BUFFER_TOO_SMALL) {
1310 zstr = zend_string_extend(zstr, zstr->len << 2, 0);
1311 goto again;
1312 }
1313 ION_CHECK(err, zend_string_release(zstr));
1314 if (zstr->len > read) {
1315 zstr = zend_string_truncate(zstr, read, 0);
1316 }
1317 RETURN_STR(zstr);
1318 }
1319
1320 static inline void php_ion_unserialize_timestamp(php_ion_reader *obj, zval *return_value)
1321 {
1322 ION_TIMESTAMP ts;
1323 ION_CHECK(ion_reader_read_timestamp(obj->reader, &ts));
1324
1325 decContext *ctx = NULL;
1326 if (obj->opt) {
1327 ctx = php_ion_obj(reader_options, obj->opt)->opt.decimal_context;
1328 }
1329
1330 object_init_ex(return_value, ce_Timestamp);
1331 php_ion_timestamp *ts_obj = php_ion_obj(timestamp, Z_OBJ_P(return_value));
1332
1333 zend_string *fmt = NULL;
1334 ts_obj->time = php_time_from_ion(&ts, ctx, &fmt);
1335 php_ion_timestamp_ctor(ts_obj, ts.precision, fmt, NULL, NULL);
1336 zend_string_release(fmt);
1337
1338 OBJ_CHECK(ts_obj);
1339 }
1340
1341 static inline void php_ion_unserialize_int(php_ion_reader *obj, zval *return_value)
1342 {
1343 ION_INT *num = NULL;
1344 ION_CHECK(ion_int_alloc(obj->reader, &num));
1345 ION_CHECK(ion_reader_read_ion_int(obj->reader, num));
1346
1347 // TODO: SIZEOF_ZEND_LONG == 4
1348 int64_t i64;
1349 iERR err = ion_int_to_int64(num, &i64);
1350 switch (err) {
1351 case IERR_OK:
1352 RETVAL_LONG(i64);
1353 goto done;
1354
1355 case IERR_NUMERIC_OVERFLOW:
1356 SIZE max, len;
1357 ION_CHECK(ion_int_char_length(num, &max));
1358 zend_string *zs = zend_string_alloc(max-1, 0);
1359
1360 err = ion_int_to_char(num, (BYTE *) zs->val, max, &len);
1361 ZEND_ASSERT(len == zs->len);
1362 RETVAL_STR(zs);
1363 /* fall through */
1364
1365 default:
1366 done:
1367 ion_int_free(num);
1368 ION_CHECK(err);
1369 }
1370 }
1371
1372 static inline void php_ion_unserialize_backref(php_ion_reader *obj, zval *return_value)
1373 {
1374 php_ion_global_unserializer *u = &php_ion_globals.unserializer;
1375 zval *backref = zend_hash_index_find(&u->ids, Z_LVAL_P(return_value));
1376
1377 if (backref) {
1378 ZVAL_COPY_VALUE(return_value, backref);
1379 zend_hash_next_index_insert(&u->addref, return_value);
1380 } else {
1381 zend_throw_exception_ex(spl_ce_RuntimeException, IERR_INTERNAL_ERROR,
1382 "Could not find backref %ld", Z_LVAL_P(return_value));
1383 }
1384 }
1385
1386 static inline void php_ion_unserialize_zval(php_ion_reader *obj, zval *return_value, ION_TYPE *typ)
1387 {
1388 ION_TYPE typ_tmp;
1389 if (!typ) {
1390 typ = &typ_tmp;
1391 ION_CHECK(ion_reader_next(obj->reader, typ));
1392 }
1393
1394 // process any annotations
1395 bool backref = false;
1396 uint8_t object_type = 0;
1397 zend_string *object_class = NULL;
1398 int32_t ann_cnt;
1399 ION_CHECK(ion_reader_get_annotation_count(obj->reader, &ann_cnt));
1400 for (int32_t i = 0; i < ann_cnt; ++i) {
1401 ION_STRING ann_str;
1402 ION_CHECK(ion_reader_get_an_annotation(obj->reader, i, &ann_str));
1403 switch (*ann_str.value) {
1404 case 'R':
1405 ZVAL_MAKE_REF(return_value);
1406 ZVAL_DEREF(return_value);
1407 zend_hash_next_index_insert(&php_ion_globals.unserializer.addref, return_value);
1408 break;
1409
1410 case 'r':
1411 // ints
1412 backref = true;
1413 break;
1414
1415 case 'S':
1416 case 'E':
1417 // strings
1418 object_type = *ann_str.value;
1419 break;
1420
1421 case 'O':
1422 case 'C':
1423 case 'o':
1424 case 'c':
1425 // structs
1426 ION_STRING class_name;
1427 ION_CHECK(ion_reader_get_an_annotation(obj->reader, ++i, &class_name));
1428 object_class = zend_string_from_ion(&class_name);
1429 object_type = *ann_str.value;
1430 break;
1431 }
1432 }
1433
1434 BOOL bval;
1435 ION_CHECK(ion_reader_is_null(obj->reader, &bval));
1436 if (bval) {
1437 goto read_null;
1438 }
1439
1440 switch (ION_TYPE_INT(*typ)) {
1441 case tid_NULL_INT:
1442 read_null: ;
1443 ION_CHECK(ion_reader_read_null(obj->reader, typ));
1444 RETURN_NULL();
1445
1446 case tid_BOOL_INT:
1447 ION_CHECK(ion_reader_read_bool(obj->reader, &bval));
1448 RETURN_BOOL(bval);
1449
1450 case tid_INT_INT:
1451 php_ion_unserialize_int(obj, return_value);
1452 if (backref) {
1453 ION_CATCH();
1454 php_ion_unserialize_backref(obj, return_value);
1455 switch (object_type) {
1456 case 0:
1457 break;
1458 case 'S':
1459 case 'E':
1460 ION_CATCH();
1461 goto from_backref_to_string;
1462 case 'c':
1463 case 'C':
1464 case 'o':
1465 case 'O':
1466 ION_CATCH();
1467 goto from_backref_to_struct;
1468 default:
1469 ZEND_ASSERT(0);
1470 }
1471 }
1472 return;
1473
1474 case tid_FLOAT_INT:
1475 double d;
1476 ION_CHECK(ion_reader_read_double(obj->reader, &d));
1477 RETURN_DOUBLE(d);
1478
1479 case tid_DECIMAL_INT:
1480 object_init_ex(return_value, ce_Decimal);
1481 php_ion_decimal *dec = php_ion_obj(decimal, Z_OBJ_P(return_value));
1482 ION_CHECK(ion_reader_read_ion_decimal(obj->reader, &dec->dec));
1483 php_ion_decimal_ctor(dec);
1484 zend_hash_next_index_insert(&php_ion_globals.unserializer.ids, return_value);
1485 return;
1486
1487 case tid_TIMESTAMP_INT:
1488 php_ion_unserialize_timestamp(obj, return_value);
1489 zend_hash_next_index_insert(&php_ion_globals.unserializer.ids, return_value);
1490 return;
1491
1492 case tid_SYMBOL_INT:
1493 ION_SYMBOL sym;
1494 ION_CHECK(ion_reader_read_ion_symbol(obj->reader, &sym));
1495 php_ion_symbol_zval(&sym, return_value);
1496 zend_hash_next_index_insert(&php_ion_globals.unserializer.ids, return_value);
1497 return;
1498
1499 case tid_STRING_INT:
1500 ION_STRING str;
1501 ION_CHECK(ion_reader_read_string(obj->reader, &str));
1502 RETVAL_STRINGL((char *) str.value, str.length);
1503 if (object_type) {
1504 from_backref_to_string: ;
1505 zend_hash_next_index_insert(&php_ion_globals.unserializer.tmp, return_value);
1506 switch (object_type) {
1507 case 'S':
1508 php_ion_unserialize_object_iface(obj, object_class, return_value);
1509 return;
1510 case 'E':
1511 // TODO
1512 return;
1513 default:
1514 ZEND_ASSERT(0);
1515 }
1516 }
1517 zend_hash_next_index_insert(&php_ion_globals.unserializer.ids, return_value);
1518 return;
1519
1520 case tid_CLOB_INT:
1521 case tid_BLOB_INT:
1522 php_ion_unserialize_lob(obj, return_value);
1523 zend_hash_next_index_insert(&php_ion_globals.unserializer.ids, return_value);
1524 return;
1525
1526 case tid_LIST_INT:
1527 case tid_SEXP_INT: // FIXME
1528 php_ion_unserialize_list(obj, return_value);
1529 if (!object_type) {
1530 return;
1531 }
1532 /* fall through */
1533
1534 case tid_STRUCT_INT:
1535 from_backref_to_struct: ;
1536 php_ion_unserialize_struct(obj, object_type, object_class, return_value);
1537 if (object_class) {
1538 zend_string_release(object_class);
1539 }
1540 return;
1541
1542 case tid_none_INT:
1543 ZEND_ASSERT(0);
1544 break;
1545
1546 case tid_DATAGRAM_INT:
1547 ZEND_ASSERT(!"datagram");
1548 case tid_EOF_INT:
1549 return;
1550 }
1551 }
1552
1553 void php_ion_unserialize(php_ion_reader *obj, zend_string *zstr, zval *return_value)
1554 {
1555 zval zreader;
1556
1557 if (obj) {
1558 ZVAL_OBJ_COPY(&zreader, &obj->std);
1559 } else {
1560 object_init_ex(&zreader, ce_Reader_Buffer_Reader);
1561 obj = php_ion_obj(reader, Z_OBJ(zreader));
1562 obj->type = BUFFER_READER;
1563 obj->buffer = zend_string_copy(zstr);
1564 php_ion_reader_ctor(obj);
1565 }
1566
1567 php_ion_globals_unserializer_step();
1568 php_ion_unserialize_zval(obj, return_value, NULL);
1569 php_ion_globals_unserializer_exit();
1570
1571 zval_ptr_dtor(&zreader);
1572 }