Skip to content

ast_canopy: guard Record against dependent/incomplete types#326

Open
isVoid wants to merge 2 commits intoNVIDIA:mainfrom
isVoid:fea/ast-canopy-record-dependent-guards
Open

ast_canopy: guard Record against dependent/incomplete types#326
isVoid wants to merge 2 commits intoNVIDIA:mainfrom
isVoid:fea/ast-canopy-record-dependent-guards

Conversation

@isVoid
Copy link
Copy Markdown
Collaborator

@isVoid isVoid commented Apr 21, 2026

Summary

Three related changes in ast_canopy/cpp/src/record.cpp:

  1. Guard size/align computation. Record::Record called ctx.getTypeSize(type) / ctx.getTypeAlign(type) on class template specializations reaching the matcher with ANCESTOR_IS_NOT_TEMPLATE. If the specialization is still dependent or incomplete (e.g. a forward-declared Fwd<int> referenced only by pointer), Clang asserts inside the size query and aborts parsing. Guard with !type->isDependentType() && !type->isIncompleteType() and emit INVALID_SIZE_OF / INVALID_ALIGN_OF sentinels when layout cannot be computed.

  2. Try/catch around per-child construction. Wrap each per-child construction (Field, Method, FunctionTemplate, nested ClassTemplate, nested Record) in try { ... } catch (...) { /* skip */ }. A single bad child declaration now skips itself instead of losing the whole parent record.

  3. Missing <limits> include. INVALID_SIZE_OF / INVALID_ALIGN_OF use std::numeric_limits; the header was transitively present in the conda build but missing in the manylinux wheel build container, so the wheel build would fail to compile without it.

Test plan

  • New sample ast_canopy/tests/data/sample_record_incomplete.cu:
    • Forward-declared Fwd<int> used only by pointer (incomplete specialization).
    • Complete Complete<float> specialization alongside (positive regression check).
  • New tests in ast_canopy/tests/test_record_incomplete.py:
    • Header with incomplete specialization parses without aborting.
    • Both UsesIncomplete and UsesComplete are returned in the struct list.
    • Complete<float> still reports a valid positive sizeof_ (no regression on layout computation).
  • <limits> include is a build-time concern and cannot be exercised from pytest; the test file docstring documents it for provenance.

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced record parsing to handle incomplete and dependent types gracefully without aborting during compilation analysis.
  • Tests

    • Added test coverage validating record parsing robustness with incomplete and dependent type specializations.

The Record constructor called ctx.getTypeSize(type) /
ctx.getTypeAlign(type) on class template specialisations reaching the
matcher with ANCESTOR_IS_NOT_TEMPLATE. If the specialisation is still
dependent or incomplete (e.g. a forward-declared Fwd<int> referenced
only by pointer), Clang asserts inside getTypeSize and aborts parsing.

Three related changes:

1. Guard the size/align computation with
   `!type->isDependentType() && !type->isIncompleteType()`. Use
   INVALID_SIZE_OF / INVALID_ALIGN_OF sentinels when layout cannot be
   computed, so downstream callers can detect and skip.

2. Wrap each per-child construction (Field, Method, FunctionTemplate,
   nested ClassTemplate, nested Record) in try/catch (...). A single
   bad child declaration now skips itself instead of losing the whole
   parent record.

3. Add a missing `<limits>` include. INVALID_SIZE_OF / INVALID_ALIGN_OF
   are defined using std::numeric_limits; the header was transitively
   present in the conda build but missing in the manylinux wheel build
   container, so the wheel build would fail to compile without it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 21, 2026

Warning

Rate limit exceeded

@isVoid has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 0 minutes and 46 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 0 minutes and 46 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ff3ccad4-1164-44f9-bf22-647c2f110e6e

📥 Commits

Reviewing files that changed from the base of the PR and between def65b0 and bbfd48d.

📒 Files selected for processing (2)
  • ast_canopy/tests/data/sample_record_incomplete.cu
  • ast_canopy/tests/test_record_incomplete.py
📝 Walkthrough

Walkthrough

The Record constructor is enhanced with try-catch blocks around element construction to gracefully skip failing components, and adds validation to prevent layout queries on dependent or incomplete types by setting size/alignment to sentinel values. Test fixture and verification tests accompany the changes.

Changes

Cohort / File(s) Summary
Core Error Handling and Type Validation
ast_canopy/cpp/src/record.cpp
Wraps construction of Field, Method, FunctionTemplate, ClassTemplate, and nested Record objects in try-catch blocks. Adds checks for dependent or incomplete record types, setting sizeof_ and alignof_ to invalid sentinel values instead of querying ASTContext.
Test Data and Verification
ast_canopy/tests/data/sample_record_incomplete.cu, ast_canopy/tests/test_record_incomplete.py
Introduces a CUDA test fixture with template types that trigger both incomplete/dependent specializations and complete specializations, paired with pytest module containing three test cases validating graceful handling of incomplete types, successful parsing of wrapper structures, and layout computation for complete specializations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A hop through error paths so graceful and kind,
Catching the exceptions before they unwind,
Types incomplete now handled with care,
Layout checks skip what shouldn't be there—
Tests bloom to bloom, both fixtures and tests aligned! 🌱

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main change: guarding the Record implementation against dependent and incomplete types by adding validation checks.
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

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ast_canopy/cpp/src/record.cpp`:
- Around line 58-63: Replace the blanket catch(...) around
fields.emplace_back(Field(FD, access)) with selective catches: rethrow critical
allocation failures by catching std::bad_alloc and then catch std::exception to
handle expected failures; inside the std::exception catch, emit a debug-only log
that includes FD->getNameAsString() (and e.what()) so skipped fields are visible
in debug builds. Apply the same pattern to the other similar catch sites in this
file (the blocks around the other Field/Method/Record construction calls at the
ranges noted) so you don't silently swallow serious errors while still
preventing non-fatal parse failures from aborting processing.

In `@ast_canopy/tests/test_record_incomplete.py`:
- Around line 61-73: Add an assertion to the test to verify incomplete
specializations get the sentinel size: after calling
parse_declarations_from_source (as in test_complete_specialization_has_layout)
locate either the UsesIncomplete struct via decls.structs (match .name ==
"UsesIncomplete") or find the Fwd<int> entry in
decls.class_template_specializations, then assert that that specialization's
sizeof_ equals the sentinel INVALID_SIZE_OF (or that sizeof_ <= 0 if the
sentinel is defined that way) to ensure incomplete templates receive the
sentinel layout value.
- Around line 36-39: The fixture source_path currently returns a file path and
causes each test to re-parse the same file; change this module-scoped fixture to
perform the parse once and return the parsed declarations (e.g., call your
parsing function used elsewhere such as parse_translation_unit / parse_file /
parse_source) instead of a path, and update tests to consume the parsed
declarations from source_path rather than re-parsing the file so parsing is
cached for the whole test module.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: b6687f9d-1036-4673-8e1d-a7bce0600ae3

📥 Commits

Reviewing files that changed from the base of the PR and between f29a975 and def65b0.

📒 Files selected for processing (3)
  • ast_canopy/cpp/src/record.cpp
  • ast_canopy/tests/data/sample_record_incomplete.cu
  • ast_canopy/tests/test_record_incomplete.py

Comment on lines +58 to +63
try {
fields.emplace_back(Field(FD, access));
} catch (...) {
// Skip fields that cannot be processed (e.g. dependent types in
// uninstantiated templates).
}
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.

🧹 Nitpick | 🔵 Trivial

Consider logging or counting skipped elements in debug builds.

The blanket catch (...) blocks silently swallow all exceptions and skip elements. While this achieves the goal of preventing a single bad child from aborting the entire record, it may make debugging difficult when legitimate issues occur.

Consider at minimum logging skipped elements in NDEBUG builds:

} catch (...) {
`#ifndef` NDEBUG
  std::cerr << "Skipped field: " << FD->getNameAsString() << std::endl;
`#endif`
}

Additionally, catching all exceptions (including std::bad_alloc) could mask serious issues. If only Clang-specific exceptions are expected, consider catching a more specific base exception type if available.

Also applies to: 72-77, 81-85, 89-93, 97-101

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ast_canopy/cpp/src/record.cpp` around lines 58 - 63, Replace the blanket
catch(...) around fields.emplace_back(Field(FD, access)) with selective catches:
rethrow critical allocation failures by catching std::bad_alloc and then catch
std::exception to handle expected failures; inside the std::exception catch,
emit a debug-only log that includes FD->getNameAsString() (and e.what()) so
skipped fields are visible in debug builds. Apply the same pattern to the other
similar catch sites in this file (the blocks around the other
Field/Method/Record construction calls at the ranges noted) so you don't
silently swallow serious errors while still preventing non-fatal parse failures
from aborting processing.

Comment on lines +36 to +39
@pytest.fixture(scope="module")
def source_path():
here = os.path.dirname(os.path.abspath(__file__))
return os.path.join(here, "data", "sample_record_incomplete.cu")
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.

🧹 Nitpick | 🔵 Trivial

Consider caching parse results to improve test efficiency.

Each test function parses the same source file independently. Consider extending the fixture to return the parsed declarations (with scope="module") to avoid redundant parsing:

♻️ Suggested refactor
 `@pytest.fixture`(scope="module")
-def source_path():
+def parsed_decls():
     here = os.path.dirname(os.path.abspath(__file__))
-    return os.path.join(here, "data", "sample_record_incomplete.cu")
+    source_path = os.path.join(here, "data", "sample_record_incomplete.cu")
+    return parse_declarations_from_source(
+        source_path, [source_path], "sm_80", bypass_parse_error=True,
+    )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ast_canopy/tests/test_record_incomplete.py` around lines 36 - 39, The fixture
source_path currently returns a file path and causes each test to re-parse the
same file; change this module-scoped fixture to perform the parse once and
return the parsed declarations (e.g., call your parsing function used elsewhere
such as parse_translation_unit / parse_file / parse_source) instead of a path,
and update tests to consume the parsed declarations from source_path rather than
re-parsing the file so parsing is cached for the whole test module.

Comment on lines +61 to +73
def test_complete_specialization_has_layout(source_path):
"""The fix must not regress layout computation for complete
specialisations: Complete<float> still gets a valid sizeof_."""
decls = parse_declarations_from_source(
source_path, [source_path], "sm_80", bypass_parse_error=True,
)
complete_specs = [
cts
for cts in decls.class_template_specializations
if "Complete" in cts.qual_name
]
assert complete_specs, "Complete<float> not in parsed specializations"
assert complete_specs[0].sizeof_ > 0, complete_specs[0].sizeof_
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.

🧹 Nitpick | 🔵 Trivial

Add assertion for UsesIncomplete having sentinel size values.

The test verifies that Complete<float> has a valid positive sizeof_, but it doesn't verify that incomplete specializations actually receive the sentinel INVALID_SIZE_OF value. Consider adding an assertion to verify the sentinel behavior:

def test_incomplete_specialization_has_invalid_layout(source_path):
    """Fwd<int> specialization should have INVALID_SIZE_OF sentinel."""
    decls = parse_declarations_from_source(
        source_path, [source_path], "sm_80", bypass_parse_error=True,
    )
    uses_incomplete = next(
        (s for s in decls.structs if s.name == "UsesIncomplete"), None
    )
    assert uses_incomplete is not None
    # Check that the struct still has a valid layout (it's complete itself)
    # but Fwd<int> specialization would have sentinel if exposed

Alternatively, if Fwd<int> appears in class_template_specializations, verify its sizeof_ equals the sentinel value.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ast_canopy/tests/test_record_incomplete.py` around lines 61 - 73, Add an
assertion to the test to verify incomplete specializations get the sentinel
size: after calling parse_declarations_from_source (as in
test_complete_specialization_has_layout) locate either the UsesIncomplete struct
via decls.structs (match .name == "UsesIncomplete") or find the Fwd<int> entry
in decls.class_template_specializations, then assert that that specialization's
sizeof_ equals the sentinel INVALID_SIZE_OF (or that sizeof_ <= 0 if the
sentinel is defined that way) to ensure incomplete templates receive the
sentinel layout value.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

Doc Preview CI
🚀 View pre-built docs at
https://NVIDIA.github.io/numbast/pr-preview/pr-326/

Preview will be ready when GitHub Pages deployment finishes.

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.

1 participant