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