diff options
| author | 2026-02-06 22:08:39 +0100 | |
|---|---|---|
| committer | 2026-02-06 22:08:39 +0100 | |
| commit | 1df039994ff2368bc64ea4bc38d9261e6153437c (patch) | |
| tree | ed16b612677c253925685a9f380abbc19d74d05b | |
| parent | b7f09ccdddc75d82ccaed867eb60e6997a7cad40 (diff) | |
| download | libplist-1df039994ff2368bc64ea4bc38d9261e6153437c.tar.gz libplist-1df039994ff2368bc64ea4bc38d9261e6153437c.tar.bz2 | |
libcnary: Define error codes and add cycle, depth, and parent guards
| -rw-r--r-- | libcnary/include/node.h | 12 | ||||
| -rw-r--r-- | libcnary/node.c | 120 | ||||
| -rw-r--r-- | libcnary/node_list.c | 99 |
3 files changed, 164 insertions, 67 deletions
diff --git a/libcnary/include/node.h b/libcnary/include/node.h index 123241a..5b3ae37 100644 --- a/libcnary/include/node.h +++ b/libcnary/include/node.h | |||
| @@ -29,6 +29,18 @@ | |||
| 29 | 29 | ||
| 30 | #define NODE_TYPE 1; | 30 | #define NODE_TYPE 1; |
| 31 | 31 | ||
| 32 | #ifndef NODE_MAX_DEPTH | ||
| 33 | #define NODE_MAX_DEPTH 512 | ||
| 34 | #endif | ||
| 35 | |||
| 36 | #define NODE_ERR_SUCCESS 0 | ||
| 37 | #define NODE_ERR_INVALID_ARG -1 | ||
| 38 | #define NODE_ERR_NO_MEM -2 | ||
| 39 | #define NODE_ERR_PARENT -3 | ||
| 40 | #define NODE_ERR_CIRCULAR_REF -4 | ||
| 41 | #define NODE_ERR_MAX_DEPTH -5 | ||
| 42 | #define NODE_ERR_NOT_FOUND -6 | ||
| 43 | |||
| 32 | // This class implements the abstract iterator class | 44 | // This class implements the abstract iterator class |
| 33 | typedef struct node* node_t; | 45 | typedef struct node* node_t; |
| 34 | struct node { | 46 | struct node { |
diff --git a/libcnary/node.c b/libcnary/node.c index 8d3708b..9457548 100644 --- a/libcnary/node.c +++ b/libcnary/node.c | |||
| @@ -61,12 +61,11 @@ node_t node_create(node_t parent, void* data) | |||
| 61 | node->children = NULL; | 61 | node->children = NULL; |
| 62 | 62 | ||
| 63 | // Pass NULL to create a root node | 63 | // Pass NULL to create a root node |
| 64 | if(parent != NULL) { | 64 | if (parent != NULL) { |
| 65 | // This is a child node so attach it to it's parent | 65 | // This is a child node so attach it to it's parent |
| 66 | error = node_attach(parent, node); | 66 | error = node_attach(parent, node); |
| 67 | if(error < 0) { | 67 | if (error < 0) { |
| 68 | // Unable to attach nodes | 68 | // Unable to attach nodes |
| 69 | printf("ERROR: %d \"Unable to attach nodes\"\n", error); | ||
| 70 | node_destroy(node); | 69 | node_destroy(node); |
| 71 | return NULL; | 70 | return NULL; |
| 72 | } | 71 | } |
| @@ -75,15 +74,86 @@ node_t node_create(node_t parent, void* data) | |||
| 75 | return node; | 74 | return node; |
| 76 | } | 75 | } |
| 77 | 76 | ||
| 77 | static int node_depth_from_root(node_t n) | ||
| 78 | { | ||
| 79 | int d = 0; | ||
| 80 | while (n && n->parent) { | ||
| 81 | d++; | ||
| 82 | n = n->parent; | ||
| 83 | if (d > NODE_MAX_DEPTH) return d; // early out | ||
| 84 | } | ||
| 85 | return d; | ||
| 86 | } | ||
| 87 | |||
| 88 | static int node_subtree_max_depth(node_t root) | ||
| 89 | { | ||
| 90 | if (!root) return 0; | ||
| 91 | |||
| 92 | typedef struct { node_t n; int depth; } frame_t; | ||
| 93 | size_t cap = 64, sp = 0; | ||
| 94 | frame_t *st = (frame_t*)malloc(cap * sizeof(*st)); | ||
| 95 | if (!st) return NODE_MAX_DEPTH + 1; | ||
| 96 | |||
| 97 | st[sp++] = (frame_t){ root, 0 }; | ||
| 98 | int maxd = 0; | ||
| 99 | |||
| 100 | while (sp) { | ||
| 101 | frame_t f = st[--sp]; | ||
| 102 | if (f.depth > maxd) maxd = f.depth; | ||
| 103 | if (maxd > NODE_MAX_DEPTH) break; | ||
| 104 | |||
| 105 | if (!f.n->children) continue; | ||
| 106 | |||
| 107 | for (node_t ch = node_first_child(f.n); ch; ch = node_next_sibling(ch)) { | ||
| 108 | if (sp == cap) { | ||
| 109 | cap *= 2; | ||
| 110 | frame_t *tmp = (frame_t*)realloc(st, cap * sizeof(*st)); | ||
| 111 | if (!tmp) { maxd = NODE_MAX_DEPTH + 1; goto out; } | ||
| 112 | st = tmp; | ||
| 113 | } | ||
| 114 | st[sp++] = (frame_t){ ch, f.depth + 1 }; | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | out: | ||
| 119 | free(st); | ||
| 120 | return maxd; | ||
| 121 | } | ||
| 122 | |||
| 123 | static int would_create_cycle(node_t parent, node_t child) | ||
| 124 | { | ||
| 125 | // if parent is anywhere in child's ancestor chain => cycle | ||
| 126 | for (node_t p = parent; p; p = p->parent) { | ||
| 127 | if (p == child) return 1; | ||
| 128 | } | ||
| 129 | return 0; | ||
| 130 | } | ||
| 131 | |||
| 78 | int node_attach(node_t parent, node_t child) | 132 | int node_attach(node_t parent, node_t child) |
| 79 | { | 133 | { |
| 80 | if (!parent || !child) return -1; | 134 | if (!parent || !child) return NODE_ERR_INVALID_ARG; |
| 81 | child->parent = parent; | 135 | |
| 82 | if(!parent->children) { | 136 | // already parented? |
| 137 | if (child->parent) return NODE_ERR_PARENT; | ||
| 138 | |||
| 139 | // self/cycle guard | ||
| 140 | if (parent == child) return NODE_ERR_CIRCULAR_REF; | ||
| 141 | if (would_create_cycle(parent, child)) return NODE_ERR_CIRCULAR_REF; | ||
| 142 | |||
| 143 | // depth guard: depth(parent)+1+max_depth(child_subtree) <= NODE_MAX_DEPTH | ||
| 144 | int pd = node_depth_from_root(parent); | ||
| 145 | int cd = node_subtree_max_depth(child); | ||
| 146 | if (pd + 1 + cd > NODE_MAX_DEPTH) { | ||
| 147 | return NODE_ERR_MAX_DEPTH; | ||
| 148 | } | ||
| 149 | |||
| 150 | if (!parent->children) { | ||
| 83 | parent->children = node_list_create(); | 151 | parent->children = node_list_create(); |
| 152 | if (!parent->children) return NODE_ERR_NO_MEM; | ||
| 84 | } | 153 | } |
| 85 | int res = node_list_add(parent->children, child); | 154 | int res = node_list_add(parent->children, child); |
| 86 | if (res == 0) { | 155 | if (res == 0) { |
| 156 | child->parent = parent; | ||
| 87 | parent->count++; | 157 | parent->count++; |
| 88 | } | 158 | } |
| 89 | return res; | 159 | return res; |
| @@ -91,7 +161,7 @@ int node_attach(node_t parent, node_t child) | |||
| 91 | 161 | ||
| 92 | int node_detach(node_t parent, node_t child) | 162 | int node_detach(node_t parent, node_t child) |
| 93 | { | 163 | { |
| 94 | if (!parent || !child) return -1; | 164 | if (!parent || !child) return NODE_ERR_INVALID_ARG; |
| 95 | int node_index = node_list_remove(parent->children, child); | 165 | int node_index = node_list_remove(parent->children, child); |
| 96 | if (node_index >= 0) { | 166 | if (node_index >= 0) { |
| 97 | parent->count--; | 167 | parent->count--; |
| @@ -101,13 +171,29 @@ int node_detach(node_t parent, node_t child) | |||
| 101 | 171 | ||
| 102 | int node_insert(node_t parent, unsigned int node_index, node_t child) | 172 | int node_insert(node_t parent, unsigned int node_index, node_t child) |
| 103 | { | 173 | { |
| 104 | if (!parent || !child) return -1; | 174 | if (!parent || !child) return NODE_ERR_INVALID_ARG; |
| 105 | child->parent = parent; | 175 | |
| 106 | if(!parent->children) { | 176 | // already parented? |
| 177 | if (child->parent) return NODE_ERR_PARENT; | ||
| 178 | |||
| 179 | // self/cycle guard | ||
| 180 | if (parent == child) return NODE_ERR_CIRCULAR_REF; | ||
| 181 | if (would_create_cycle(parent, child)) return NODE_ERR_CIRCULAR_REF; | ||
| 182 | |||
| 183 | // depth guard: depth(parent)+1+max_depth(child_subtree) <= NODE_MAX_DEPTH | ||
| 184 | int pd = node_depth_from_root(parent); | ||
| 185 | int cd = node_subtree_max_depth(child); | ||
| 186 | if (pd + 1 + cd > NODE_MAX_DEPTH) { | ||
| 187 | return NODE_ERR_MAX_DEPTH; | ||
| 188 | } | ||
| 189 | |||
| 190 | if (!parent->children) { | ||
| 107 | parent->children = node_list_create(); | 191 | parent->children = node_list_create(); |
| 192 | if (!parent->children) return NODE_ERR_NO_MEM; | ||
| 108 | } | 193 | } |
| 109 | int res = node_list_insert(parent->children, node_index, child); | 194 | int res = node_list_insert(parent->children, node_index, child); |
| 110 | if (res == 0) { | 195 | if (res == 0) { |
| 196 | child->parent = parent; | ||
| 111 | parent->count++; | 197 | parent->count++; |
| 112 | } | 198 | } |
| 113 | return res; | 199 | return res; |
| @@ -186,7 +272,7 @@ node_t node_next_sibling(node_t node) | |||
| 186 | 272 | ||
| 187 | int node_child_position(node_t parent, node_t child) | 273 | int node_child_position(node_t parent, node_t child) |
| 188 | { | 274 | { |
| 189 | if (!parent || !parent->children || !parent->children->begin || !child) return -1; | 275 | if (!parent || !parent->children || !parent->children->begin || !child) return NODE_ERR_INVALID_ARG; |
| 190 | int node_index = 0; | 276 | int node_index = 0; |
| 191 | int found = 0; | 277 | int found = 0; |
| 192 | node_t ch; | 278 | node_t ch; |
| @@ -198,7 +284,7 @@ int node_child_position(node_t parent, node_t child) | |||
| 198 | node_index++; | 284 | node_index++; |
| 199 | } | 285 | } |
| 200 | if (!found) { | 286 | if (!found) { |
| 201 | return -1; | 287 | return NODE_ERR_NOT_FOUND; |
| 202 | } | 288 | } |
| 203 | return node_index; | 289 | return node_index; |
| 204 | } | 290 | } |
| @@ -211,10 +297,18 @@ node_t node_copy_deep(node_t node, copy_func_t copy_func) | |||
| 211 | data = copy_func(node->data); | 297 | data = copy_func(node->data); |
| 212 | } | 298 | } |
| 213 | node_t copy = node_create(NULL, data); | 299 | node_t copy = node_create(NULL, data); |
| 300 | if (!copy) return NULL; | ||
| 214 | node_t ch; | 301 | node_t ch; |
| 215 | for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) { | 302 | for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) { |
| 216 | node_t cc = node_copy_deep(ch, copy_func); | 303 | node_t cc = node_copy_deep(ch, copy_func); |
| 217 | node_attach(copy, cc); | 304 | if (!cc) { |
| 305 | node_destroy(copy); | ||
| 306 | return NULL; | ||
| 307 | } | ||
| 308 | if (node_attach(copy, cc) < 0) { | ||
| 309 | node_destroy(copy); | ||
| 310 | return NULL; | ||
| 311 | } | ||
| 218 | } | 312 | } |
| 219 | return copy; | 313 | return copy; |
| 220 | } | 314 | } |
diff --git a/libcnary/node_list.c b/libcnary/node_list.c index f6c2c70..218c81d 100644 --- a/libcnary/node_list.c +++ b/libcnary/node_list.c | |||
| @@ -49,7 +49,7 @@ node_list_t node_list_create() | |||
| 49 | 49 | ||
| 50 | int node_list_add(node_list_t list, node_t node) | 50 | int node_list_add(node_list_t list, node_t node) |
| 51 | { | 51 | { |
| 52 | if (!list || !node) return -1; | 52 | if (!list || !node) return NODE_ERR_INVALID_ARG; |
| 53 | 53 | ||
| 54 | // Find the last element in the list | 54 | // Find the last element in the list |
| 55 | node_t last = list->end; | 55 | node_t last = list->end; |
| @@ -72,89 +72,80 @@ int node_list_add(node_list_t list, node_t node) | |||
| 72 | 72 | ||
| 73 | // Increment our node count for this list | 73 | // Increment our node count for this list |
| 74 | list->count++; | 74 | list->count++; |
| 75 | return 0; | 75 | return NODE_ERR_SUCCESS; |
| 76 | } | 76 | } |
| 77 | 77 | ||
| 78 | int node_list_insert(node_list_t list, unsigned int node_index, node_t node) | 78 | int node_list_insert(node_list_t list, unsigned int node_index, node_t node) |
| 79 | { | 79 | { |
| 80 | if (!list || !node) return -1; | 80 | if (!list || !node) return NODE_ERR_INVALID_ARG; |
| 81 | if (node_index >= list->count) { | 81 | if (node_index > list->count) return NODE_ERR_INVALID_ARG; |
| 82 | if (node_index == list->count) { | ||
| 82 | return node_list_add(list, node); | 83 | return node_list_add(list, node); |
| 83 | } | 84 | } |
| 84 | 85 | ||
| 85 | // Get the first element in the list | 86 | // Get the first element in the list |
| 86 | node_t cur = list->begin; | 87 | node_t cur = list->begin; |
| 87 | |||
| 88 | unsigned int pos = 0; | ||
| 89 | node_t prev = NULL; | 88 | node_t prev = NULL; |
| 90 | 89 | ||
| 91 | if (node_index > 0) { | 90 | for (unsigned int pos = 0; pos < node_index; pos++) { |
| 92 | while (pos < node_index) { | 91 | if (!cur) return NODE_ERR_INVALID_ARG; |
| 93 | prev = cur; | 92 | prev = cur; |
| 94 | cur = cur->next; | 93 | cur = cur->next; |
| 95 | pos++; | ||
| 96 | } | ||
| 97 | } | 94 | } |
| 98 | 95 | ||
| 96 | // insert node before cur | ||
| 97 | node->prev = prev; | ||
| 98 | node->next = cur; | ||
| 99 | |||
| 99 | if (prev) { | 100 | if (prev) { |
| 100 | // Set previous node | ||
| 101 | node->prev = prev; | ||
| 102 | // Set next node of our new node to next node of the previous node | ||
| 103 | node->next = prev->next; | ||
| 104 | // Set next node of previous node to our new node | ||
| 105 | prev->next = node; | 101 | prev->next = node; |
| 106 | } else { | 102 | } else { |
| 107 | node->prev = NULL; | ||
| 108 | // get old first element in list | ||
| 109 | node->next = list->begin; | ||
| 110 | // set new node as first element in list | ||
| 111 | list->begin = node; | 103 | list->begin = node; |
| 112 | } | 104 | } |
| 113 | 105 | ||
| 114 | if (node->next == NULL) { | 106 | if (cur) { |
| 115 | // Set the lists prev to the new last element | 107 | cur->prev = node; |
| 116 | list->end = node; | ||
| 117 | } else { | 108 | } else { |
| 118 | // set prev of the new next element to our node | 109 | // should not happen with bounds above, but keeps things consistent |
| 119 | node->next->prev = node; | 110 | list->end = node; |
| 120 | } | 111 | } |
| 121 | 112 | ||
| 122 | // Increment our node count for this list | 113 | // Increment our node count for this list |
| 123 | list->count++; | 114 | list->count++; |
| 124 | return 0; | 115 | return NODE_ERR_SUCCESS; |
| 125 | } | 116 | } |
| 126 | 117 | ||
| 118 | // Returns removed index (>=0) on success, or NODE_ERR_* (<0) on failure. | ||
| 127 | int node_list_remove(node_list_t list, node_t node) | 119 | int node_list_remove(node_list_t list, node_t node) |
| 128 | { | 120 | { |
| 129 | if (!list || !node) return -1; | 121 | if (!list || !node) return NODE_ERR_INVALID_ARG; |
| 130 | if (list->count == 0) return -1; | 122 | if (list->count == 0) return NODE_ERR_NOT_FOUND; |
| 131 | 123 | ||
| 132 | int node_index = 0; | 124 | int node_index = 0; |
| 133 | node_t n; | 125 | for (node_t n = list->begin; n; n = n->next, node_index++) { |
| 134 | for (n = list->begin; n; n = n->next) { | 126 | if (node != n) continue; |
| 135 | if (node == n) { | 127 | |
| 136 | node_t newnode = node->next; | 128 | node_t newnode = node->next; |
| 137 | if (node->prev) { | 129 | if (node->prev) { |
| 138 | node->prev->next = newnode; | 130 | node->prev->next = newnode; |
| 139 | if (newnode) { | 131 | } else { |
| 140 | newnode->prev = node->prev; | 132 | // we just removed the first element |
| 141 | } else { | 133 | list->begin = newnode; |
| 142 | // last element in the list | ||
| 143 | list->end = node->prev; | ||
| 144 | } | ||
| 145 | } else { | ||
| 146 | // we just removed the first element | ||
| 147 | if (newnode) { | ||
| 148 | newnode->prev = NULL; | ||
| 149 | } else { | ||
| 150 | list->end = NULL; | ||
| 151 | } | ||
| 152 | list->begin = newnode; | ||
| 153 | } | ||
| 154 | list->count--; | ||
| 155 | return node_index; | ||
| 156 | } | 134 | } |
| 157 | node_index++; | 135 | |
| 136 | if (newnode) { | ||
| 137 | newnode->prev = node->prev; | ||
| 138 | } else { | ||
| 139 | // we removed the last element, set new end | ||
| 140 | list->end = node->prev; | ||
| 141 | } | ||
| 142 | |||
| 143 | // fully detach node from list | ||
| 144 | node->prev = NULL; | ||
| 145 | node->next = NULL; | ||
| 146 | |||
| 147 | list->count--; | ||
| 148 | return node_index; | ||
| 158 | } | 149 | } |
| 159 | return -1; | 150 | return NODE_ERR_NOT_FOUND; |
| 160 | } | 151 | } |
