Skip to content

Fix JSON reader guards for scatter validity, validation, and max nesting depth - #22452

Merged
rapids-bot[bot] merged 8 commits into
NVIDIA:release/26.06from
karthikeyann:fix-json_memory_error
May 18, 2026
Merged

Fix JSON reader guards for scatter validity, validation, and max nesting depth#22452
rapids-bot[bot] merged 8 commits into
NVIDIA:release/26.06from
karthikeyann:fix-json_memory_error

Conversation

@karthikeyann

@karthikeyann karthikeyann commented May 11, 2026

Copy link
Copy Markdown
Contributor

Description

Tightens JSON reader handling for malformed and deeply nested inputs by avoiding null validity-mask writes, fixing strict value validation, handling high-bit input bytes safely, and reporting an error when nesting exceeds the supported TreeDepthT range.

Adds unit coverage for recovery-mode malformed records, invalid unquoted values, and nesting-depth boundaries.

Checklist

  • I am familiar with the Contributing Guidelines.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

@karthikeyann
karthikeyann requested a review from a team as a code owner May 11, 2026 16:21
@copy-pr-bot

copy-pr-bot Bot commented May 11, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@karthikeyann
karthikeyann requested review from shrshi and vuule May 11, 2026 16:21
@github-actions github-actions Bot added the libcudf Affects libcudf (C++/CUDA) code. label May 11, 2026
@karthikeyann karthikeyann added bug Something isn't working 3 - Ready for Review Ready for review by team tests Unit testing for project cuIO cuIO issue non-breaking Non-breaking change labels May 11, 2026
@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved JSON reader robustness: better validation for malformed input and recovery behavior
    • Added device-side bounds checking for JSON nesting depth to prevent overflow and assert out-of-range detection
    • Fixed numeric-token validation and refined null-mask handling for struct/list/string values
    • Corrected tokenizer handling of high-bit bytes to avoid incorrect token classification
  • Tests

    • Added regression tests for malformed JSON recovery and nesting-depth edge cases

Walkthrough

This PR hardens JSON parsing by guarding validity-mask writes, fixing non-ASCII and numeric-token handling, adding device-checked nesting-depth casting with an atomic out-of-range flag, exporting token-stream validation, and expanding tests for malformed inputs and depth limits.

Changes

JSON Parsing Robustness: Validity Masks, Character Encoding, Depth Bounds, and Token Validation

Layer / File(s) Summary
Validity Mask Safety in Offset Scattering
cpp/src/io/json/host_tree_algorithms.cu
scatter_offsets now guards set_bit calls for NC_STRUCT, NC_LIST, NC_STR, and NC_VAL with validity-pointer checks and performs ignore_vals checks before validity writes for string/value nodes.
Nesting Depth Bounds Checking Implementation
cpp/src/io/json/json_tree.cu
Adds CUDA atomics/functional/iterator/limits includes, introduces checked_token_level_output, runs the push/pop scan in int32_t with transform-output casting to TreeDepthT, records out-of-range via cuda::atomic_ref, and asserts no out-of-range after the scan.
Public API Visibility and Metadata
cpp/src/io/json/nested_json.hpp
Updates SPDX year range and adds CUDF_EXPORT to the validate_token_stream declaration (symbol visibility change only).
Character Encoding and Numeric Validation Fixes
cpp/src/io/json/nested_json_gpu.cu, cpp/src/io/json/process_tokens.cu
PdaSymbolToSymbolGroupId now casts symbol bytes to unsigned char before signed-to-int conversion to avoid negative indexes; numeric-validation fixes correct the leading-digit check to compare c against '0'..'9'.
Test Expansion: Malformed JSON and Depth Limits
cpp/tests/io/json/json_test.cpp, cpp/tests/io/json/nested_json_test.cpp
Test includes reorganized; new tests added for malformed field names and structural chars (RECOVER_WITH_NULL), non-ASCII bytes in strings, unquoted-value rejection, and nesting-depth boundary conditions (exceeding and at limits).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • devavret
  • pmattione-nvidia
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title accurately and concisely summarizes the main changes: fixes to JSON reader guards for scatter validity, validation, and max nesting depth.
Description check ✅ Passed The pull request description is directly related to the changeset, explaining the core fixes being made and what test coverage was added.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Comment @coderabbitai help to get the list of available commands and usage tips.

@karthikeyann karthikeyann changed the title Fix missing limits and checks in JSON reader code Fix JSON reader guards for scatter validity, validation, and max nesting depth May 11, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cpp/src/io/json/process_tokens.cu (1)

115-169: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject exponents directly after . in strict numeric validation.

number_state::SAW_RADIX still transitions to START_EXPONENT, so inputs like 1.e2 and 0.e1 are accepted even though JSON requires at least one fractional digit after the decimal point. That leaves strict validation too permissive in the same path this PR is tightening.

Suggested fix
             case number_state::SAW_RADIX:
               if (c >= '0' && c <= '9') {
                 num_state = number_state::FRACTION;
-              } else if ('e' == c || 'E' == c) {
-                num_state = number_state::START_EXPONENT;
               } else {
                 return false;
               }
               break;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/io/json/process_tokens.cu` around lines 115 - 169, The SAW_RADIX
state currently allows an exponent transition (to number_state::START_EXPONENT),
which accepts forms like "1.e2"; change the number_state::SAW_RADIX branch so it
only accepts a digit (transition to number_state::FRACTION) and otherwise
returns false—remove the 'e'/'E' -> START_EXPONENT branch so an exponent is only
allowed after at least one fractional digit; update the switch case in
process_tokens.cu handling number_state::SAW_RADIX accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cpp/tests/io/json/json_test.cpp`:
- Around line 1605-1611: The loop that verifies root column names uses
a_child_col_names.size() as its bound, so the final root column ("d") is never
checked; change the loop bound to iterate over root_col_names.size() (or
new_reader_table.metadata.schema_info.size()) instead so every entry in
new_reader_table.metadata.schema_info is compared against root_col_names[i] in
that for loop.

---

Outside diff comments:
In `@cpp/src/io/json/process_tokens.cu`:
- Around line 115-169: The SAW_RADIX state currently allows an exponent
transition (to number_state::START_EXPONENT), which accepts forms like "1.e2";
change the number_state::SAW_RADIX branch so it only accepts a digit (transition
to number_state::FRACTION) and otherwise returns false—remove the 'e'/'E' ->
START_EXPONENT branch so an exponent is only allowed after at least one
fractional digit; update the switch case in process_tokens.cu handling
number_state::SAW_RADIX accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: cebd270f-24a7-449c-b56c-8dfba460e276

📥 Commits

Reviewing files that changed from the base of the PR and between 5b88237 and f9784d5.

📒 Files selected for processing (7)
  • cpp/src/io/json/host_tree_algorithms.cu
  • cpp/src/io/json/json_tree.cu
  • cpp/src/io/json/nested_json.hpp
  • cpp/src/io/json/nested_json_gpu.cu
  • cpp/src/io/json/process_tokens.cu
  • cpp/tests/io/json/json_test.cpp
  • cpp/tests/io/json/nested_json_test.cpp

Comment thread cpp/tests/io/json/json_test.cpp

@vuule vuule 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.

bunch of questions mostly
looks good in general

Comment thread cpp/src/io/json/json_tree.cu Outdated
Comment thread cpp/src/io/json/json_tree.cu Outdated
? static_cast<int32_t>(newline)
: (symbol == newline ? static_cast<int32_t>(whitespace) : static_cast<int32_t>(symbol));
: (symbol == newline ? static_cast<int32_t>(whitespace)
: static_cast<int32_t>(static_cast<unsigned char>(symbol)));

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.

Why a chain of casts? is this clamping to unsigned char range?

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.

Yes.

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.

Can we add a brief comment here explaining that we need this chain of casts?

Comment thread cpp/tests/io/json/nested_json_test.cpp Outdated
Comment thread cpp/tests/io/json/nested_json_test.cpp Outdated
@mhaseeb123 mhaseeb123 moved this to Burndown in libcudf May 11, 2026
@karthikeyann
karthikeyann force-pushed the fix-json_memory_error branch from f9784d5 to c9e4a06 Compare May 12, 2026 18:04
@karthikeyann
karthikeyann requested a review from vuule May 12, 2026 18:05
Comment thread cpp/src/io/json/json_tree.cu Outdated
Comment thread cpp/tests/io/json/nested_json_test.cpp Outdated
Comment thread cpp/tests/io/json/nested_json_test.cpp Outdated
Comment thread cpp/tests/io/json/nested_json_test.cpp Outdated
Comment thread cpp/tests/io/json/json_test.cpp

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
cpp/src/io/json/json_tree.cu (1)

320-325: ⚡ Quick win

Keep the CUDF_EXPECTS predicate side-effect free.

Pull depth_out_of_range.value(stream) into a local first, then pass that local into CUDF_EXPECTS. That keeps the sync/read explicit instead of hiding it inside the macro condition.

♻️ Proposed fix
-    CUDF_EXPECTS(
-      !depth_out_of_range.value(stream),
+    auto const is_depth_out_of_range = depth_out_of_range.value(stream);
+    CUDF_EXPECTS(
+      !is_depth_out_of_range,
       "JSON token nesting depth is outside the supported range for TreeDepthT [" +
         std::to_string(static_cast<size_type>(cuda::std::numeric_limits<TreeDepthT>::min())) +
         ", " +
         std::to_string(static_cast<size_type>(cuda::std::numeric_limits<TreeDepthT>::max())) + "]");

As per coding guidelines, CUDF_EXPECTS condition must be a pure predicate with no side effects.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/io/json/json_tree.cu` around lines 320 - 325, The CUDF_EXPECTS
predicate currently calls depth_out_of_range.value(stream) which causes a hidden
read; instead call depth_out_of_range.value(stream) once into a local bool
(e.g., bool depth_bad = depth_out_of_range.value(stream)) just before the
CUDF_EXPECTS line and then pass that local (depth_bad) into CUDF_EXPECTS so the
macro predicate is side-effect free; update the surrounding message to use
TreeDepthT and the same variables as before but do not perform any stream reads
inside CUDF_EXPECTS.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cpp/src/io/json/json_tree.cu`:
- Around line 132-145: The depth-out-of-range check in
checked_token_level_output is being applied to all tokens (including
non-materialized closing tokens) and must be moved so only materialized nodes
are validated: remove the narrowing check from checked_token_level_output and
perform it when writing node levels in the copy_if path (the code that uses
is_node and writes into node_levels), or filter non-node tokens before running
the scan so the scan’s output never exceeds TreeDepthT; also remove the
device-to-host side-effect from the CUDF_EXPECTS predicate by not calling
value(stream) inside the predicate—pass a pure boolean or precomputed host-side
value instead so the predicate remains side-effect-free.

---

Nitpick comments:
In `@cpp/src/io/json/json_tree.cu`:
- Around line 320-325: The CUDF_EXPECTS predicate currently calls
depth_out_of_range.value(stream) which causes a hidden read; instead call
depth_out_of_range.value(stream) once into a local bool (e.g., bool depth_bad =
depth_out_of_range.value(stream)) just before the CUDF_EXPECTS line and then
pass that local (depth_bad) into CUDF_EXPECTS so the macro predicate is
side-effect free; update the surrounding message to use TreeDepthT and the same
variables as before but do not perform any stream reads inside CUDF_EXPECTS.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 42295f5b-5598-4dda-9ad0-225e386edd0c

📥 Commits

Reviewing files that changed from the base of the PR and between c9e4a06 and 2758403.

📒 Files selected for processing (4)
  • cpp/src/io/json/json_tree.cu
  • cpp/src/io/json/nested_json_gpu.cu
  • cpp/tests/io/json/json_test.cpp
  • cpp/tests/io/json/nested_json_test.cpp
🚧 Files skipped from review as they are similar to previous changes (2)
  • cpp/tests/io/json/nested_json_test.cpp
  • cpp/tests/io/json/json_test.cpp

Comment thread cpp/src/io/json/json_tree.cu
@karthikeyann
karthikeyann requested review from shrshi and vuule May 15, 2026 16:23
@vuule
vuule force-pushed the fix-json_memory_error branch from db58b1d to 758714a Compare May 15, 2026 22:17
@vuule
vuule changed the base branch from main to release/26.06 May 15, 2026 22:17
@vuule vuule added 5 - Ready to Merge Testing and reviews complete, ready to merge and removed 3 - Ready for Review Ready for review by team labels May 15, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
cpp/src/io/json/host_tree_algorithms.cu (1)

911-911: 💤 Low value

Redundant check: already verified at lambda entry.

The condition d_ignore_vals[col_ids[i]] is already checked at Line 898 with an early return. If execution reaches this switch case, the condition must be false, making this check dead code.

♻️ Suggested cleanup
       case NC_STR: [[fallthrough]];
       case NC_VAL:
-        if (d_ignore_vals[col_ids[i]]) break;
         if (d_columns_data[col_ids[i]].validity)
           set_bit(d_columns_data[col_ids[i]].validity, row_offsets[i]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/io/json/host_tree_algorithms.cu` at line 911, Remove the redundant
check for d_ignore_vals[col_ids[i]] inside the switch case: since the lambda
containing this switch already returns early when d_ignore_vals[col_ids[i]] is
true (the prior guard at the lambda entry), delete the "if
(d_ignore_vals[col_ids[i]]) break;" line to avoid dead code and rely on the
existing early-return guard that uses d_ignore_vals, keeping the logic in the
switch case focused on the active paths for col_ids[i].
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@cpp/src/io/json/host_tree_algorithms.cu`:
- Line 911: Remove the redundant check for d_ignore_vals[col_ids[i]] inside the
switch case: since the lambda containing this switch already returns early when
d_ignore_vals[col_ids[i]] is true (the prior guard at the lambda entry), delete
the "if (d_ignore_vals[col_ids[i]]) break;" line to avoid dead code and rely on
the existing early-return guard that uses d_ignore_vals, keeping the logic in
the switch case focused on the active paths for col_ids[i].

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: b409b1d3-0232-428f-9bde-0c3f57c24e9c

📥 Commits

Reviewing files that changed from the base of the PR and between 2758403 and 758714a.

📒 Files selected for processing (7)
  • cpp/src/io/json/host_tree_algorithms.cu
  • cpp/src/io/json/json_tree.cu
  • cpp/src/io/json/nested_json.hpp
  • cpp/src/io/json/nested_json_gpu.cu
  • cpp/src/io/json/process_tokens.cu
  • cpp/tests/io/json/json_test.cpp
  • cpp/tests/io/json/nested_json_test.cpp
🚧 Files skipped from review as they are similar to previous changes (5)
  • cpp/src/io/json/process_tokens.cu
  • cpp/src/io/json/nested_json_gpu.cu
  • cpp/src/io/json/nested_json.hpp
  • cpp/src/io/json/json_tree.cu
  • cpp/tests/io/json/nested_json_test.cpp

@karthikeyann

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit 9a9c726 into NVIDIA:release/26.06 May 18, 2026
120 of 121 checks passed
@mhaseeb123 mhaseeb123 moved this from Burndown to Landed in libcudf May 18, 2026
madsbk pushed a commit to madsbk/cudf that referenced this pull request May 19, 2026
…ing depth (NVIDIA#22452)

Tightens JSON reader handling for malformed and deeply nested inputs by avoiding null validity-mask writes, fixing strict value validation, handling high-bit input bytes safely, and reporting an error when nesting exceeds the supported `TreeDepthT` range.

Adds unit coverage for recovery-mode malformed records, invalid unquoted values, and nesting-depth boundaries.

Authors:
  - Karthikeyan (https://github.com/karthikeyann)

Approvers:
  - Shruti Shivakumar (https://github.com/shrshi)
  - Vukasin Milovanovic (https://github.com/vuule)

URL: NVIDIA#22452
rapids-bot Bot pushed a commit that referenced this pull request May 19, 2026
Fixes a memcheck error introduced by #22452 where an atomic operation on a bool variable is reported by compute-sanitizer as an out-of-bounds access. Changing the variable to an `int32_t` resolves the error.

Closes #22570

Authors:
  - David Wendt (https://github.com/davidwendt)

Approvers:
  - Bradley Dice (https://github.com/bdice)
  - Yunsong Wang (https://github.com/PointKernel)

URL: #22571
@GregoryKimball GregoryKimball removed this from libcudf Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

5 - Ready to Merge Testing and reviews complete, ready to merge bug Something isn't working cuIO cuIO issue libcudf Affects libcudf (C++/CUDA) code. non-breaking Non-breaking change tests Unit testing for project

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants