Return nil from Row#field when the offset is past the end - #366
Open
youdie006 wants to merge 1 commit into
Open
Conversation
Array#[start..-1] returns [] when start == length but nil once start is
greater, so the slice in Row#field and Row#index vanishes and the
:assoc / :index send lands on nil:
row = CSV::Row.new(%w{A B C A A}, %w{1 2 3 4 5})
row.field("A", 5) # => nil
row.field("A", 6) # => NoMethodError: undefined method `assoc' for nil
The docs promise nil unconditionally ("Returns +nil+ if the header does
not exist", "Returns +nil+ if +index+ is out of range"), and offset 5 on
a 5-field row already returns nil, so offset 6 returning nil is the only
self-consistent behaviour. Negative offsets past the start break the
same way.
Five entry points reach it: #field, its alias #[], #values_at, #index,
and #delete.
The existing assertions stop at the last offset that happens to work
(field("A", 5)), one short of the bug.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
CSV::Row#field(header, offset)raisesNoMethodErroronce the offset goes one past the end of the row, where the docs promisenil.Five public entry points reach it —
#field, its alias#[],#values_at,#index, and#delete:Cause
lib/csv/row.rb:206Array#[start..-1]returns[]whenstart == length, butniloncestartis greater — so the receiver ofassoc/[]disappears.lib/csv/row.rb:575has the same shape and is what#indexand#deletego through:Why this is the code and not the docs
The documented contract is unconditional:
and the neighbouring offset already behaves that way: on a 5-field row,
field("A", 5)returnsnil. Offset 6 returningnilis the only self-consistent reading.Why it was not caught
test/csv/test_row.rb:70-75walks the offsets up to exactly the last one that works:The row has 5 fields, so it stops one short of the bug.
The change
Guard the slice at both sites. Assertions added to the existing
test_fieldblock for offset 6, a far-past offset, and the#[]/#index/#values_atpaths.Verification
Full suite: 527 tests, 4047 assertions, 0 failures, 0 errors — before and after.
Reverting only
lib/csv/row.rband keeping the new assertions fails withNoMethodError: undefined method 'assoc' for nil:NilClass, so they exercise this bug rather than#fieldin general.Disclosure: this patch was prepared with AI assistance. The reproduction, the red/green check and the suite runs above were executed against this branch; happy to adjust anything on request.