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