Skip to content

Comments

Fix fragile List::binarySearch() usage#10047

Open
cmarcelo wants to merge 1 commit intoshader-slang:masterfrom
cmarcelo:fix-fragile-binary-search-usage
Open

Fix fragile List::binarySearch() usage#10047
cmarcelo wants to merge 1 commit intoshader-slang:masterfrom
cmarcelo:fix-fragile-binary-search-usage

Conversation

@cmarcelo
Copy link
Contributor

@cmarcelo cmarcelo commented Feb 16, 2026

The binarySearch() was comparing pointer addresses on the member list
that is ordered by the declaration position. That may not necessarily
work unless is guaranteed that the allocator produces pointers in
declaration order and they are not reordered. Use indexOf() instead.

See bfae49d ("Mediate access to ContainerDecl members (#7242)") for context.

@cmarcelo cmarcelo requested a review from a team as a code owner February 16, 2026 22:12
Copilot AI review requested due to automatic review settings February 16, 2026 22:12
@CLAassistant
Copy link

CLAassistant commented Feb 16, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

No actionable comments were generated in the recent review. 🎉


📝 Walkthrough

Walkthrough

Replaced binarySearch with indexOf to locate subAncestor and supAncestor in their common ancestor's member list when computing declaration ordering; the arithmetic (supIndex - subIndex) and control flow are unchanged.

Changes

Cohort / File(s) Summary
Declaration ordering change
source/slang/slang-check-decl.cpp
Replaced use of binarySearch with indexOf to obtain indices of subAncestor and supAncestor in the common ancestor's member list; preserved the existing arithmetic (supIndex - subIndex) and overall behavior.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

I’m a rabbit in the codewood, hopping light and spry 🐇
Swapped a search for an index under a quiet sky ✨
Two lines hopped places, order kept the same,
I nibble a carrot and whisper the function’s name.
Hooray for tiny changes that keep the codegame high!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix fragile List::binarySearch() usage' directly and specifically describes the main change—replacing binarySearch with indexOf to fix unreliable pointer address comparisons.
Description check ✅ Passed The description clearly explains why the change was needed (pointer address comparison unreliability) and what was changed (binarySearch replaced with indexOf), directly relating to the changeset.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@cmarcelo
Copy link
Contributor Author

This patch was motivated by #10048.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes fragile usage of List::binarySearch() in the semantic checking of generic type equality constraints. The old code incorrectly used binarySearch() with pointer comparison on a member list ordered by declaration position, which only worked by chance because the allocator happened to produce pointers in declaration order. This is replaced with the existing helper function _compareDeclsInCommonParentByOrderOfDeclaration() that properly compares declarations by their position in the parent container, following the guidance from PR #7242 to use mediated access to ContainerDecl members.

Changes:

  • Replaced fragile binarySearch() usage with proper declaration order comparison helper function
  • Added forward declaration for _compareDeclsInCommonParentByOrderOfDeclaration()

@cmarcelo cmarcelo force-pushed the fix-fragile-binary-search-usage branch from ecc4e7a to a9dd060 Compare February 17, 2026 01:02
Copy link

@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: 1

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

Inline comments:
In `@source/slang/slang-check-decl.cpp`:
- Around line 3581-3582: The subtraction using auto subIndex =
ancestor->getMembers().indexOf(subAncestor) and auto supIndex =
ancestor->getMembers().indexOf(supAncestor) must guard against indexOf returning
-1; if either subIndex or supIndex is -1, avoid using int(supIndex - subIndex)
and instead fall back to the existing compareDecls(...) logic (or assert/handle
error) to determine ordering. Update the code around where subIndex/supIndex are
computed so you check (subIndex < 0 || supIndex < 0) and call compareDecls(…)
when invalid, otherwise keep the current subtraction path.

Comment on lines +3581 to +3582
auto subIndex = ancestor->getMembers().indexOf(subAncestor);
auto supIndex = ancestor->getMembers().indexOf(supAncestor);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, locate and examine the List class definition and indexOf method
rg -B2 -A10 'indexOf\(' --type cpp --type hpp | head -100

Repository: shader-slang/slang

Length of output: 89


🏁 Script executed:

# Check Index type definition
rg 'typedef.*Index|using.*Index' --type hpp | head -30

Repository: shader-slang/slang

Length of output: 89


🏁 Script executed:

# Examine the context at lines 3581-3582
sed -n '3570,3600p' source/slang/slang-check-decl.cpp

Repository: shader-slang/slang

Length of output: 1031


🏁 Script executed:

# Search for GenericDecl definition and its member structure
rg -B2 -A15 'class GenericDecl' --type hpp | head -80

Repository: shader-slang/slang

Length of output: 89


🏁 Script executed:

# Check ContainerDecl::getMembers implementation
rg -B2 -A10 'getMembers\(\)' --type hpp --type cpp | head -100

Repository: shader-slang/slang

Length of output: 89


🏁 Script executed:

# Search for List class and indexOf method definition
rg 'indexOf' -A5 -B2 --type c --type h | head -150

Repository: shader-slang/slang

Length of output: 7585


🏁 Script executed:

# Check Index type definition
rg 'typedef.*Index|using.*Index' --type h | head -50

Repository: shader-slang/slang

Length of output: 3685


🏁 Script executed:

# Search for GenericDecl class definition
rg -A20 'class GenericDecl' --type h | head -100

Repository: shader-slang/slang

Length of output: 5103


🏁 Script executed:

# Check ContainerDecl implementation
rg -A10 'class ContainerDecl' --type h | head -150

Repository: shader-slang/slang

Length of output: 2891


🏁 Script executed:

# Search for getMembers in headers
rg -B2 -A8 'getMembers\(' --type h --type c | head -100

Repository: shader-slang/slang

Length of output: 1361


🏁 Script executed:

# Get complete indexOf implementation from slang-list.h
sed -n '1,/template.*indexOf/p' source/core/slang-list.h | tail -40

Repository: shader-slang/slang

Length of output: 891


🏁 Script executed:

# Find the full indexOf method with return statement
rg -B2 -A15 'template.*indexOf\(const T2' source/core/slang-list.h

Repository: shader-slang/slang

Length of output: 44


🏁 Script executed:

# Search for getDirectMemberDecls implementation
rg -B2 -A15 'getDirectMemberDecls' --type c --type h | head -150

Repository: shader-slang/slang

Length of output: 9271


🏁 Script executed:

# Check if FIDDLE() marked fields are included in member lists
rg -B5 -A10 'FIDDLE.*inner' source/slang/slang-ast-decl.h

Repository: shader-slang/slang

Length of output: 532


🏁 Script executed:

# Look for how GenericDecl members are enumerated
rg -B10 -A10 'class GenericDecl' source/slang/slang-ast-decl.h

Repository: shader-slang/slang

Length of output: 602


🏁 Script executed:

# Find getDirectMemberDecls implementation in cpp files
rg -B2 -A20 'getDirectMemberDecls\(\)' --type c | head -100

Repository: shader-slang/slang

Length of output: 6349


🏁 Script executed:

# Search for how GenericDecl members are handled in the codebase
rg 'GenericDecl.*inner' --type c --type h | head -50

Repository: shader-slang/slang

Length of output: 44


🏁 Script executed:

# Find the complete indexOf implementation with return statement
rg -B2 -A8 'Index indexOf' source/core/slang-list.h

Repository: shader-slang/slang

Length of output: 284


🏁 Script executed:

# Search for getDirectMemberDecls implementation in cpp
rg -B3 -A15 'List<Decl\*> const& ContainerDecl::getDirectMemberDecls' --type c

Repository: shader-slang/slang

Length of output: 44


🏁 Script executed:

# Also check slang-ast-decl.cpp for implementation
grep -n 'getDirectMemberDecls' source/slang/slang-ast-decl.cpp | head -20

Repository: shader-slang/slang

Length of output: 196


🏁 Script executed:

# Look at what fields are included for GenericDecl member enumeration
rg -B5 -A20 'class GenericDecl' source/slang/slang-ast-decl.cpp

Repository: shader-slang/slang

Length of output: 44


🏁 Script executed:

# Check how FIDDLE macro works - does it include fields in member lists?
rg -B2 -A5 'FIDDLE\(\)' source/slang/slang-ast-decl.h | grep -A5 'GenericDecl'

Repository: shader-slang/slang

Length of output: 385


🏁 Script executed:

# Read the implementation at line 304
sed -n '300,330p' source/slang/slang-ast-decl.cpp

Repository: shader-slang/slang

Length of output: 687


🏁 Script executed:

# Look for how _directMemberDecls is populated for GenericDecl
rg '_directMemberDecls' --type c --type h | grep -i generic

Repository: shader-slang/slang

Length of output: 44


🏁 Script executed:

# Search for how GenericDecl handles its inner decl in member operations
rg -B5 -A10 'GenericDecl.*addMember\|addDirectMemberDecl.*GenericDecl' --type c

Repository: shader-slang/slang

Length of output: 44


🏁 Script executed:

# Check if there's special handling for GenericDecl's inner in the context of checkGenericTypeEqualityConstraintSubType
sed -n '3550,3600p' source/slang/slang-check-decl.cpp

Repository: shader-slang/slang

Length of output: 1938


🏁 Script executed:

# Look for tests or usages that show expected behavior when ancestor is GenericDecl
rg -B10 -A10 'findDeclsLowestCommonAncestor.*GenericDecl\|GenericDecl.*indexOf' --type c

Repository: shader-slang/slang

Length of output: 44


🏁 Script executed:

# Check the actual field structure - is inner a direct member or a special field?
rg -B10 -A30 'class GenericDecl' source/slang/slang-ast-decl.h | head -60

Repository: shader-slang/slang

Length of output: 1027


🏁 Script executed:

# Search for how GenericTypeConstraintDecl relates to GenericDecl
rg -B5 -A15 'class GenericTypeConstraintDecl' source/slang/slang-ast-decl.h

Repository: shader-slang/slang

Length of output: 819


🏁 Script executed:

# Look for where generic parameters are added as members
rg -B5 -A10 'addMember.*GenericTypeParam\|GenericTypeParam.*addMember' --type c | head -50

Repository: shader-slang/slang

Length of output: 44


🏁 Script executed:

# Check if there's defensive code elsewhere that handles indexOf returning -1
rg -B3 -A3 'indexOf.*!= -1\|indexOf.*>= 0\|indexOf.*< 0' --type c --type h | head -80

Repository: shader-slang/slang

Length of output: 44


🏁 Script executed:

# Let me look at the broader context of checkGenericTypeEqualityConstraintSubType
sed -n '3550,3620p' source/slang/slang-check-decl.cpp

Repository: shader-slang/slang

Length of output: 2569


🏁 Script executed:

# Check findDeclsLowestCommonAncestor to understand what it returns
rg -B5 -A20 'findDeclsLowestCommonAncestor' --type c --type h | head -100

Repository: shader-slang/slang

Length of output: 44


🏁 Script executed:

# Look for safeguards elsewhere in the codebase when using indexOf result
rg -B2 -A5 'indexOf.*-1' source/core/slang-list.h

Repository: shader-slang/slang

Length of output: 237


🏁 Script executed:

# Search for where GenericTypeParamDecl instances are added as members
rg -B10 -A10 'GenericTypeParamDecl.*addMember\|addDirectMemberDecl.*Param' --type c | head -50

Repository: shader-slang/slang

Length of output: 44


🏁 Script executed:

# Look at the broader generic decl creation flow
rg -B5 -A15 'new GenericTypeParamDecl\|GenericTypeParamDecl\(' --type c | head -80

Repository: shader-slang/slang

Length of output: 44


🏁 Script executed:

# See if there's any documentation about what getMembers returns for GenericDecl
rg -B3 -A3 'GenericDecl.*getMembers\|getMembers.*GenericDecl' --type c --type h

Repository: shader-slang/slang

Length of output: 44


🏁 Script executed:

# Check if there are any assertions or checks before using indexOf result elsewhere
rg -B2 -A2 'subIndex.*supIndex\|supIndex.*subIndex' --type c

Repository: shader-slang/slang

Length of output: 44


🏁 Script executed:

# Final check: confirm Index(-1) is the standard sentinel by looking at contains patterns
rg 'indexOf.*!= -1\|indexOf.*== -1\|!= Index\(-1\)' source/core/slang-list.h source/core/slang-array.h

Repository: shader-slang/slang

Length of output: 44


Add guards against indexOf returning -1 to prevent incorrect index subtraction.

At lines 3581-3582, indexOf can return -1 if subAncestor or supAncestor is not present in ancestor->getMembers(). The direct subtraction int(supIndex - subIndex) then produces incorrect ordering: (-1 - 0 = -1), (0 - (-1) = 1), or (-1 - (-1) = 0), with the zero case silently triggering the diagnostic at line 3593. Check both indices and either assert they are valid or fall back to compareDecls.

Suggested guard
        auto subIndex = ancestor->getMembers().indexOf(subAncestor);
        auto supIndex = ancestor->getMembers().indexOf(supAncestor);
+       if (subIndex == Index(-1) || supIndex == Index(-1))
+           return compareDecls(subAncestor, supAncestor);
        return int(supIndex - subIndex);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
auto subIndex = ancestor->getMembers().indexOf(subAncestor);
auto supIndex = ancestor->getMembers().indexOf(supAncestor);
auto subIndex = ancestor->getMembers().indexOf(subAncestor);
auto supIndex = ancestor->getMembers().indexOf(supAncestor);
if (subIndex == Index(-1) || supIndex == Index(-1))
return compareDecls(subAncestor, supAncestor);
return int(supIndex - subIndex);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/slang/slang-check-decl.cpp` around lines 3581 - 3582, The subtraction
using auto subIndex = ancestor->getMembers().indexOf(subAncestor) and auto
supIndex = ancestor->getMembers().indexOf(supAncestor) must guard against
indexOf returning -1; if either subIndex or supIndex is -1, avoid using
int(supIndex - subIndex) and instead fall back to the existing compareDecls(...)
logic (or assert/handle error) to determine ordering. Update the code around
where subIndex/supIndex are computed so you check (subIndex < 0 || supIndex < 0)
and call compareDecls(…) when invalid, otherwise keep the current subtraction
path.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would someone help verify if it is applicable, is it a valid concern? My own understanding here is that we expect both to be found.

@cmarcelo
Copy link
Contributor Author

@coderabbitai summary

@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

✅ Actions performed

Summary regeneration triggered.

@szihs szihs added the pr: non-breaking PRs without breaking changes label Feb 19, 2026
@szihs szihs requested a review from jkwak-work February 19, 2026 10:53
The binarySearch() was comparing pointer addresses on the member list
that is ordered by the declaration position.  That may not necessarily
work unless is guaranteed that the allocator produces pointers in
declaration order and they are not reordered.  Use indexOf() instead.

See bfae49d ("Mediate access to ContainerDecl members (shader-slang#7242)") for
context.
@cmarcelo cmarcelo force-pushed the fix-fragile-binary-search-usage branch from a9dd060 to c707bf4 Compare February 22, 2026 18:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr: non-breaking PRs without breaking changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants