summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorGravatar Nikias Bassen2026-02-10 17:45:12 +0100
committerGravatar Nikias Bassen2026-02-10 17:45:12 +0100
commit8c78d89041b713bffcb0b09fee4468304a3a54d5 (patch)
tree2c9427e2382b47c2aaf724a074fadafde415c066 /src
parent9ef0d05265198ede1fd14271ab3f4812d34ebe2e (diff)
downloadlibplist-8c78d89041b713bffcb0b09fee4468304a3a54d5.tar.gz
libplist-8c78d89041b713bffcb0b09fee4468304a3a54d5.tar.bz2
plist: Make plist copy and free implementations iterative
Convert plist_free_node() and plist_copy_node() to iterative implementations. This avoids unbounded recursion and stack overflow when handling deeply nested plist data, while preserving existing semantics and caches.
Diffstat (limited to 'src')
-rw-r--r--src/plist.c253
-rw-r--r--src/plist.h6
2 files changed, 211 insertions, 48 deletions
diff --git a/src/plist.c b/src/plist.c
index 31f754d..ea285e0 100644
--- a/src/plist.c
+++ b/src/plist.c
@@ -413,30 +413,59 @@ void plist_free_data(plist_data_t data)
413 } 413 }
414} 414}
415 415
416static int plist_free_node(node_t node) 416static int plist_free_node(node_t root)
417{ 417{
418 if (!node) return NODE_ERR_INVALID_ARG; 418 if (!root) return NODE_ERR_INVALID_ARG;
419 plist_data_t data = NULL; 419
420 int node_index = -1; 420 int root_index = -1;
421 421
422 if (node->parent) { 422 if (root->parent) {
423 node_index = node_detach(node->parent, node); 423 root_index = node_detach(root->parent, root);
424 if (root_index < 0) {
425 return root_index;
426 }
424 } 427 }
425 428
426 data = plist_get_data(node); 429 size_t cap = 64, sp = 0;
427 plist_free_data(data); 430 node_t *stack = (node_t*)malloc(cap * sizeof(*stack));
428 node->data = NULL; 431 if (!stack) return NODE_ERR_NO_MEM;
429 432
430 node_t ch; 433 stack[sp++] = root;
431 for (ch = node_first_child(node); ch; ) { 434
432 node_t next = node_next_sibling(ch); 435 while (sp) {
433 plist_free_node(ch); 436 node_t node = stack[sp - 1];
434 ch = next; 437 node_t ch = node_first_child(node);
435 } 438 if (ch) {
439 int di = node_detach(node, ch);
440 if (di < 0) {
441 free(stack);
442 return di;
443 }
444
445 if (sp == cap) {
446 cap += 64;
447 node_t *tmp = (node_t*)realloc(stack, cap * sizeof(*stack));
448 if (!tmp) {
449 free(stack);
450 return NODE_ERR_NO_MEM;
451 }
452 stack = tmp;
453 }
454 stack[sp++] = ch;
455 continue;
456 }
457
458 plist_data_t data = plist_get_data(node);
459 plist_free_data(data);
460 node->data = NULL;
461
462 node_destroy(node);
436 463
437 node_destroy(node); 464 sp--;
465 }
438 466
439 return node_index; 467 free(stack);
468 return root_index;
440} 469}
441 470
442plist_t plist_new_dict(void) 471plist_t plist_new_dict(void)
@@ -572,36 +601,43 @@ void plist_mem_free(void* ptr)
572 } 601 }
573} 602}
574 603
575static plist_t plist_copy_node(node_t node) 604static int plist_copy_node_shallow(node_t node, plist_t *out_newnode, plist_data_t *out_newdata, plist_type *out_type)
576{ 605{
577 plist_type node_type = PLIST_NONE; 606 if (!node || !out_newnode || !out_newdata || !out_type) return NODE_ERR_INVALID_ARG;
578 plist_t newnode = NULL; 607
579 plist_data_t data = plist_get_data(node); 608 plist_data_t data = plist_get_data(node);
580 plist_data_t newdata = plist_new_plist_data(); 609 if (!data) return NODE_ERR_INVALID_ARG;
581 610
582 assert(data); // plist should always have data 611 plist_data_t newdata = plist_new_plist_data();
583 assert(newdata); 612 if (!newdata) return NODE_ERR_NO_MEM;
584 613
585 memcpy(newdata, data, sizeof(struct plist_data_s)); 614 memcpy(newdata, data, sizeof(struct plist_data_s));
586 615
587 node_type = plist_get_node_type(node); 616 plist_type node_type = plist_get_node_type(node);
588 switch (node_type) { 617 switch (node_type) {
589 case PLIST_DATA: 618 case PLIST_DATA:
590 if (data->buff) { 619 if (data->buff) {
591 newdata->buff = (uint8_t *) malloc(data->length); 620 newdata->buff = (uint8_t*)malloc(data->length);
592 assert(newdata->buff); 621 if (!newdata->buff) {
622 plist_free_data(newdata);
623 return NODE_ERR_NO_MEM;
624 }
593 memcpy(newdata->buff, data->buff, data->length); 625 memcpy(newdata->buff, data->buff, data->length);
594 } else { 626 } else {
595 newdata->buff = NULL; 627 newdata->buff = NULL;
596 newdata->length = 0; 628 newdata->length = 0;
597 } 629 }
598 break; 630 break;
631
599 case PLIST_KEY: 632 case PLIST_KEY:
600 case PLIST_STRING: 633 case PLIST_STRING:
601 if (data->strval) { 634 if (data->strval) {
602 size_t n = strlen(data->strval); 635 size_t n = strlen(data->strval);
603 newdata->strval = (char*)malloc(n+1); 636 newdata->strval = (char*)malloc(n+1);
604 assert(newdata->strval); 637 if (!newdata->strval) {
638 plist_free_data(newdata);
639 return NODE_ERR_NO_MEM;
640 }
605 memcpy(newdata->strval, data->strval, n+1); 641 memcpy(newdata->strval, data->strval, n+1);
606 newdata->length = (uint64_t)n; 642 newdata->length = (uint64_t)n;
607 } else { 643 } else {
@@ -609,59 +645,180 @@ static plist_t plist_copy_node(node_t node)
609 newdata->length = 0; 645 newdata->length = 0;
610 } 646 }
611 break; 647 break;
648
612 case PLIST_ARRAY: 649 case PLIST_ARRAY:
613 if (data->hashtable) { 650 if (data->hashtable) {
614 ptrarray_t* pa = ptr_array_new(((ptrarray_t*)data->hashtable)->capacity); 651 ptrarray_t* pa = ptr_array_new(((ptrarray_t*)data->hashtable)->capacity);
615 assert(pa); 652 if (!pa) {
653 plist_free_data(newdata);
654 return NODE_ERR_NO_MEM;
655 }
616 newdata->hashtable = pa; 656 newdata->hashtable = pa;
617 } 657 }
618 break; 658 break;
659
619 case PLIST_DICT: 660 case PLIST_DICT:
620 if (data->hashtable) { 661 if (data->hashtable) {
621 hashtable_t* ht = hash_table_new(dict_key_hash, dict_key_compare, NULL); 662 hashtable_t* ht = hash_table_new(dict_key_hash, dict_key_compare, NULL);
622 assert(ht); 663 if (!ht) {
664 plist_free_data(newdata);
665 return NODE_ERR_NO_MEM;
666 }
623 newdata->hashtable = ht; 667 newdata->hashtable = ht;
624 } 668 }
625 break; 669 break;
670
626 default: 671 default:
627 break; 672 break;
628 } 673 }
629 newnode = plist_new_node(newdata);
630 674
631 node_t ch; 675 plist_t newnode = plist_new_node(newdata);
632 unsigned int node_index = 0; 676 if (!newnode) {
633 for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) { 677 plist_free_data(newdata);
634 /* copy child node */ 678 return NODE_ERR_NO_MEM;
635 plist_t newch = plist_copy_node(ch); 679 }
636 if (!newch) { 680
637 plist_free_node((node_t)newnode); 681 *out_newnode = newnode;
682 *out_newdata = newdata;
683 *out_type = node_type;
684 return NODE_ERR_SUCCESS;
685}
686
687static plist_t plist_copy_node(node_t root)
688{
689 typedef struct copy_frame {
690 node_t orig; // original node
691 plist_t copy; // copied node
692 plist_data_t copydata; // copied node's data (for cache updates)
693 plist_type type; // copied node type
694 node_t next_child; // next child of orig to process
695 unsigned int node_index; // child index (for dict key/value odd/even)
696 int depth; // optional depth tracking
697 } copy_frame_t;
698
699 if (!root) return NULL;
700
701 // shallow-copy root first
702 plist_t newroot = NULL;
703 plist_data_t newroot_data = NULL;
704 plist_type newroot_type = PLIST_NONE;
705
706 int r = plist_copy_node_shallow(root, &newroot, &newroot_data, &newroot_type);
707 if (r != NODE_ERR_SUCCESS) {
708 PLIST_ERR("%s: shallow node copy failed (%d)\n", __func__, r);
709 return NULL;
710 }
711
712 // stack of frames
713 size_t cap = 64, sp = 0;
714 copy_frame_t *st = (copy_frame_t*)malloc(cap * sizeof(*st));
715 if (!st) {
716 plist_free_node((node_t)newroot);
717 return NULL;
718 }
719
720 copy_frame_t cf;
721 cf.orig = root;
722 cf.copy = newroot;
723 cf.copydata = newroot_data;
724 cf.type = newroot_type;
725 cf.next_child = node_first_child(root);
726 cf.node_index = 0;
727 cf.depth = 0;
728 st[sp++] = cf;
729
730 while (sp) {
731 copy_frame_t *f = &st[sp - 1];
732
733 if (f->depth > NODE_MAX_DEPTH) {
734 plist_free_node((node_t)newroot);
735 free(st);
736 PLIST_ERR("%s: maximum nesting depth exceeded\n", __func__);
737 return NULL;
738 }
739
740 // done with this node?
741 if (!f->next_child) {
742 sp--;
743 continue;
744 }
745
746 // take next child and advance iterator
747 node_t ch = f->next_child;
748 f->next_child = node_next_sibling(ch);
749
750 // shallow copy child
751 plist_t newch = NULL;
752 plist_data_t newch_data = NULL;
753 plist_type newch_type = PLIST_NONE;
754
755 r = plist_copy_node_shallow(ch, &newch, &newch_data, &newch_type);
756 if (r != NODE_ERR_SUCCESS) {
757 plist_free_node((node_t)newroot);
758 free(st);
759 PLIST_ERR("%s: shallow node copy failed (%d)\n", __func__, r);
638 return NULL; 760 return NULL;
639 } 761 }
640 /* attach to new parent node */ 762
641 int r = node_attach((node_t)newnode, (node_t)newch); 763 // attach child to copied parent
764 r = node_attach((node_t)f->copy, (node_t)newch);
642 if (r != NODE_ERR_SUCCESS) { 765 if (r != NODE_ERR_SUCCESS) {
643 plist_free_node((node_t)newch); 766 plist_free_node((node_t)newch);
644 plist_free_node((node_t)newnode); 767 plist_free_node((node_t)newroot);
768 free(st);
769 PLIST_ERR("%s: failed to attach child to copied parent (%d)\n", __func__, r);
645 return NULL; 770 return NULL;
646 } 771 }
647 /* if needed, add child node to lookup table of parent node */ 772
648 switch (node_type) { 773 // update lookup cache on the *parent* copy
774 switch (f->type) {
649 case PLIST_ARRAY: 775 case PLIST_ARRAY:
650 if (newdata->hashtable) { 776 if (f->copydata->hashtable) {
651 ptr_array_add((ptrarray_t*)newdata->hashtable, newch); 777 ptr_array_add((ptrarray_t*)f->copydata->hashtable, newch);
652 } 778 }
653 break; 779 break;
780
654 case PLIST_DICT: 781 case PLIST_DICT:
655 if (newdata->hashtable && (node_index % 2 != 0)) { 782 if (f->copydata->hashtable && (f->node_index % 2 != 0)) {
656 hash_table_insert((hashtable_t*)newdata->hashtable, (node_prev_sibling((node_t)newch))->data, newch); 783 node_t new_key = node_prev_sibling((node_t)newch);
784 if (new_key) {
785 hash_table_insert((hashtable_t*)f->copydata->hashtable, new_key->data, newch);
786 }
657 } 787 }
658 break; 788 break;
789
659 default: 790 default:
660 break; 791 break;
661 } 792 }
662 node_index++; 793
794 f->node_index++;
795
796 // push child frame to process its children
797 if (sp == cap) {
798 cap += 64;
799 copy_frame_t *tmp = (copy_frame_t*)realloc(st, cap * sizeof(*st));
800 if (!tmp) {
801 plist_free_node((node_t)newroot);
802 free(st);
803 PLIST_ERR("%s: out of memory when reallocating\n", __func__);
804 return NULL;
805 }
806 st = tmp;
807 }
808
809 copy_frame_t nf;
810 nf.orig = ch;
811 nf.copy = newch;
812 nf.copydata = newch_data;
813 nf.type = newch_type;
814 nf.next_child = node_first_child(ch);
815 nf.node_index = 0;
816 nf.depth = f->depth + 1;
817 st[sp++] = nf;
663 } 818 }
664 return newnode; 819
820 free(st);
821 return newroot;
665} 822}
666 823
667plist_t plist_copy(plist_t node) 824plist_t plist_copy(plist_t node)
diff --git a/src/plist.h b/src/plist.h
index e310bcc..7228696 100644
--- a/src/plist.h
+++ b/src/plist.h
@@ -49,9 +49,15 @@
49 #endif 49 #endif
50#endif 50#endif
51 51
52#include "node.h"
53
52#ifndef PLIST_MAX_NESTING_DEPTH 54#ifndef PLIST_MAX_NESTING_DEPTH
55#ifdef NODE_MAX_DEPTH
56#define PLIST_MAX_NESTING_DEPTH NODE_MAX_DEPTH
57#else
53#define PLIST_MAX_NESTING_DEPTH 512 58#define PLIST_MAX_NESTING_DEPTH 512
54#endif 59#endif
60#endif
55 61
56#include "plist/plist.h" 62#include "plist/plist.h"
57 63