Repository navigation
Check every C next pointer before printing levels - #373
Conversation
|
If you want to close an issue without review, you MUST address the reasons. |
|
I closed it too early. cubic’s finding on the fixed 4096-entry arrays is valid: |
ccce5f0 to
18095e5
Compare
18095e5 to
b4fa32b
Compare
b4fa32b to
5d0d15a
Compare
This comment has been minimized.
This comment has been minimized.
148e6d5 to
08953b3
Compare
08953b3 to
fdf98e2
Compare
| static int compare_graph_neighbor_ids(const void* a, const void* b) { int left = *(const int*)a; int right = *(const int*)b; return (left > right) - (left < right); } | ||
| static void json_graph(FILE* out, struct Node* node) { if (!node) { fputs("[]", out); return; } struct Node* nodes[1024]; struct Node* seen[1024] = {0}; int count = 0; nodes[count++] = node; for (int i = 0; i < count && count < 1024; i++) { struct Node* cur = nodes[i]; if (isOriginalGraphNode(cur)) { json_string(out, "Returned graph must be a deep copy."); return; } if (cur->val <= 0 || cur->val >= 1024) { json_string(out, "Returned graph node IDs must be between 1 and 1023."); return; } seen[cur->val] = cur; for (int j = 0; j < cur->numNeighbors; j++) { struct Node* next = cur->neighbors[j]; if (!next) { json_string(out, "Returned graph has a NULL neighbor."); return; } if (next->val <= 0 || next->val >= 1024) { json_string(out, "Returned graph node IDs must be between 1 and 1023."); return; } if (isOriginalGraphNode(next)) { json_string(out, "Returned graph must be a deep copy."); return; } if (seen[next->val] && seen[next->val] != next) { json_string(out, "Returned graph must not contain distinct nodes with the same ID."); return; } if (!seen[next->val]) { seen[next->val] = next; nodes[count++] = next; } } } fputc('[', out); for (int value = 1, printed = 0; value < 1024; value++) { struct Node* cur = NULL; for (int i = 0; i < count; i++) if (nodes[i]->val == value) cur = nodes[i]; if (!cur) continue; if (printed++) fputc(',', out); int degree = cur->numNeighbors; int* neighborIds = NULL; if (degree > 0) { neighborIds = malloc((size_t)degree * sizeof(*neighborIds)); if (!neighborIds) exit(EXIT_FAILURE); for (int j = 0; j < degree; j++) neighborIds[j] = cur->neighbors[j]->val; qsort(neighborIds, (size_t)degree, sizeof(*neighborIds), compare_graph_neighbor_ids); } json_int_array(out, neighborIds, degree); free(neighborIds); } fputc(']', out); } | ||
| static void json_next_tree(FILE* out, struct Node* root) { if (!root) { fputs("[]", out); return; } struct Node* queue[4096]; int head = 0, tail = 0, firstLevel = 1; queue[tail++] = root; fputc('[', out); while (head < tail) { int end = tail; if (!firstLevel) fputc(',', out); firstLevel = 0; fputc('[', out); for (int i = head; i < end; i++) { if (i > head) fputc(',', out); fprintf(out, "%d", queue[i]->val); if (queue[i]->next != (i + 1 < end ? queue[i + 1] : NULL)) { fputs("]", out); fputs("]", out); return; } if (queue[i]->left) queue[tail++] = queue[i]->left; if (queue[i]->right) queue[tail++] = queue[i]->right; } fputc(']', out); head = end; } fputc(']', out); } | ||
| static void json_next_tree(FILE* out, struct Node* root) { if (!root) { fputs("[]", out); return; } int capacity = 64, head = 0, tail = 0, levels = 0; struct Node** queue = malloc(capacity * sizeof(*queue)); int* levelEnd = malloc(capacity * sizeof(*levelEnd)); if (!queue || !levelEnd) { free(queue); free(levelEnd); fputs("null,\\"error\\":", out); json_string(out, "Out of memory while checking next pointers."); return; } queue[tail++] = root; while (head < tail) { int end = tail; for (int i = head; i < end; i++) { if (queue[i]->next != (i + 1 < end ? queue[i + 1] : NULL)) { fputs("null,\\"error\\":", out); json_string(out, "next pointers do not match the tree level order."); free(queue); free(levelEnd); return; } for (int side = 0; side < 2; side++) { struct Node* child = side ? queue[i]->right : queue[i]->left; if (!child) continue; for (int seen = 0; seen < tail; seen++) if (queue[seen] == child) { fputs("null,\\"error\\":", out); json_string(out, "Returned tree must not reach a node twice."); free(queue); free(levelEnd); return; } if (tail == capacity) { struct Node** grownQueue = realloc(queue, 2 * capacity * sizeof(*queue)); if (grownQueue) queue = grownQueue; int* grownEnds = grownQueue ? realloc(levelEnd, 2 * capacity * sizeof(*levelEnd)) : NULL; if (grownEnds) levelEnd = grownEnds; if (!grownQueue || !grownEnds) { free(queue); free(levelEnd); fputs("null,\\"error\\":", out); json_string(out, "Out of memory while checking next pointers."); return; } capacity *= 2; } queue[tail++] = child; } } levelEnd[levels++] = end; head = end; } fputc('[', out); for (int level = 0, start = 0; level < levels; start = levelEnd[level++]) { if (level) fputc(',', out); fputc('[', out); for (int i = start; i < levelEnd[level]; i++) { if (i > start) fputc(',', out); fprintf(out, "%d", queue[i]->val); } fputc(']', out); } fputc(']', out); free(queue); free(levelEnd); } |
There was a problem hiding this comment.
The repeated-node guard exists only here. The same self-child solution still hangs the other four serializers: the C++ nextTreeToJson, the Java nextTreeJson, and the next-tree walks in web/runners.js add one more level per loop with next == NULL, so the next check never fires and the candidate gets a timeout or OOM instead of "Returned tree must not reach a node twice.". This can be a follow-up: give each a seen set over queued nodes with the same message, plus the self-child case from this test for each language.
There was a problem hiding this comment.
#428 covers these four, the C++ nextTreeToJson, the Java nextTreeJson and both walks in web/runners.js, with the same message and a self-child case for each, and quill0224 is assigned to it. This PR stays with json_next_tree, as scoped there.
fdf98e2 to
5d8a5e9
Compare
The C harness for the next-pointer tree printed each value before checking that node's next pointer, and on a mismatch closed the brackets and returned. The result was a shorter list with no message, and a wrong next on the last node of the bottom row printed exactly the expected levels, so C accepted a solution that C++, Java, JavaScript and Python reject. The harness now checks every pointer first and reports the message the other languages raise in the result's error field, as json_list does. Its queue grows with the tree instead of holding 4096 entries on the stack, since a candidate case can reach the problem's 6000-node limit, and a failed allocation is reported the same way rather than written through. A node already in the queue is refused, since a node that is its own child passes every next check and would grow the queue until memory runs out. Closes sysprog21#370
5d8a5e9 to
e70fa1c
Compare
|
Thank @winter77777 for contributing! |
The C harness for Org Chart Row Navigation printed each level's values before checking each node's
nextpointer, and on a mismatch it closed the brackets and returned without a message. A wrongnexton the last node of the bottom row therefore printed exactly the expected levels, and the solution in #370 passed 5/5 in C while C++, Java, JavaScript and Python fail it 1/5.json_next_treenow checks everynextpointer before printing anything. On a mismatch it reports "next pointers do not match the tree level order.", the same message the other languages raise, in the result'serrorfield asjson_listdoes. A candidate case without an expected answer then shows as failed instead of showing the message as its output.Review turned up two more problems. The queue was a fixed 4096-entry array on the stack, and a candidate case with 5000 nodes (the problem allows 6000) ended the run with "stack smashing detected", on
mainas well. It is now allocated withmallocand doubled through temporary pointers. If an allocation fails, both buffers are freed and the harness reports "Out of memory while checking next pointers." A returned node that is its own child also passed everynextcheck while the queue doubled until memory ran out, so a node already in the queue is now refused with "Returned tree must not reach a node twice.", the messagejson_treeuses since #388. The queue grows only after a child passes that check, and an empty tree still prints[]before anything is allocated.For the solution in #370:
[[1],[2,3],[4,5,7]], passes[], passes[], passes[[1]], passes[[1],[2,3],[5,7]], passes[[1],[2]], passesValidation on Ubuntu 24.04 under WSL2, over 8ecf93b:
tests/browser/compiler-explorer.test.jsruns the judge's five cases plus a 64-node tree that fills the first queue exactly, with every node holding the same value, and a 6000-node tree. A correct solution must print every expected value. A wrongnexton the bottom row's last node, or on the root's left child pointing back at the root, must be reported as a mismatch. A last node that is its own left child, a last node whose left child is the root, and a root whose two children are the same node must be reported as a repeat. Builds whosereallocalways fails or fails only for the level ends, and two whosemallocloses only the queue or only the level ends, must report running out of memory exactly where an allocation is lost, and the self-child and back-to-root solutions must still report the repeat up to 64 nodes. Onmainthe test fails at its first build with "stack smashing detected", and it also fails when the harness is weakened by hand, for example to check only the last pointer of a level, only whether a pointer is NULL, or values instead of addresses.-fsanitize=address,undefined, these solutions and forcedmallocandreallocfailures run without a report.Closes #370
Summary by cubic
Fixes the C harness for "Populating Next Right Pointers in Each Node II" so it validates every
nextpointer before printing levels, instead of letting a wrongnexton the last node of the bottom row pass silently.json_next_treenow prints"next pointers do not match the tree level order.", matching the C++, Java, JavaScript, and Python harnesses, and it also refuses any node already reached by printing"Returned tree must not reach a node twice.""Out of memory while checking next pointers."reallocandmallocpaths that verify the out-of-memory message.Closes #370.
Written for commit e70fa1c. Summary will update on new commits.