Skip to content

Check every C next pointer before printing levels - #373

Merged
jserv merged 1 commit into
sysprog21:mainfrom
winter77777:c-next-tree-check
Oct 23, 2026
Merged

jserv merged 1 commit into
sysprog21:mainfrom
winter77777:c-next-tree-check

Conversation

@winter77777

@winter77777 winter77777 commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

The C harness for Org Chart Row Navigation printed each level's values before checking each node's next pointer, and on a mismatch it closed the brackets and returned without a message. A wrong next on 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_tree now checks every next pointer 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's error field as json_list does. 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 main as well. It is now allocated with malloc and 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 every next check 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 message json_tree uses 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:

Case Before After
sparse final level [[1],[2,3],[4,5,7]], passes message, fails
empty tree [], passes [], passes
single node [[1]], passes message, fails
missing middle children [[1],[2,3],[5,7]], passes message, fails
one child [[1],[2]], passes message, fails

Validation on Ubuntu 24.04 under WSL2, over 8ecf93b:

  • The toolchain test in tests/browser/compiler-explorer.test.js runs 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 wrong next on 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 whose realloc always fails or fails only for the level ends, and two whose malloc loses 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. On main the 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.
  • Under -fsanitize=address,undefined, these solutions and forced malloc and realloc failures 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 next pointer before printing levels, instead of letting a wrong next on the last node of the bottom row pass silently.

  • On a mismatch, json_next_tree now 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."
  • The traversal queue now grows dynamically instead of holding a fixed 4096 entries, since a candidate case can reach the problem's 6000-node limit; a failed allocation reports "Out of memory while checking next pointers."
  • Adds a test covering a correct solution, a ringed bottom row, a self-child, a 6000-node tree, and failing realloc and malloc paths that verify the out-of-memory message.

Closes #370.

Written for commit e70fa1c. Summary will update on new commits.

View guided diff

cubic-dev-ai[bot]

This comment was marked as resolved.

@jserv

jserv commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

If you want to close an issue without review, you MUST address the reasons.

@winter77777

Copy link
Copy Markdown
Contributor Author

I closed it too early. cubic’s finding on the fixed 4096-entry arrays is valid:
a candidate case with 5000 nodes, within the problem’s 6000-node limit, ends the C run with “stack smashing detected”, on main as well as on this branch.
I will fix it here with a test that fails first, then reopen this PR

Comment thread web/compiler-explorer.js Outdated
cubic-dev-ai[bot]

This comment was marked as resolved.

Comment thread web/compiler-explorer.js Outdated
@jserv

This comment has been minimized.

@jserv jserv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebase latest main branch to pick up the CI fixes from #434.

Comment thread web/compiler-explorer.js
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); }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#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.

Comment thread tests/browser/compiler-explorer.test.js
Comment thread tests/browser/compiler-explorer.test.js Outdated
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
@jserv
jserv merged commit a6733f4 into sysprog21:main Oct 23, 2026
10 checks passed
@jserv

jserv commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Thank @winter77777 for contributing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

C tests accept a wrong next pointer on the last node in Org Chart Row Navigation

2 participants