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