-
Notifications
You must be signed in to change notification settings - Fork 1.7k
ruby: refine rb/uninitialized-local-variable
#19205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
1ca25b2
ruby: add test of `rb/uninitialized-local-variable`
yoff 53c88da
ruby: refine query for uninitialised local variables
yoff 8555e8c
ruby: add change notes
yoff f675a14
ruby: remove redundant cases
yoff 4167e96
ruby: more complete impleemntation of `isInBooleanContext`
yoff 6e2cfab
ruby: add test for `for`
yoff b641d5f
ruby: fix FP
yoff 2477233
ruby: only report on method calls
yoff 6a76a40
ruby: adjust change notes
yoff eb0f8e9
ruby: add `rb/uninitialized-local-variable` to quality suite
yoff 85e27ca
Merge branch 'main' into ruby/refine-uninitialised-local
yoff b988be8
ruby: improve help file
yoff 7517272
ruby: remove repetitive change note
yoff File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
4 changes: 4 additions & 0 deletions
4
ruby/ql/src/change-notes/2025-04-02-adjust-uninitialized-local-alert-message.md
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
--- | ||
category: majorAnalysis | ||
--- | ||
* The query `rb/uninitialized-local-variable` now only produces alerts when the variable is the receiver of a method call and should produce very few false positives. It also now comes with a help file. |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# Method call on `nil` | ||
|
||
## Description | ||
In Ruby, it is not necessary to explicitly initialize variables. | ||
If a local variable has not been explicitly initialized, it will have the value `nil`. If this happens unintended, though, the variable will not represent an object with the expected methods, and a method call on the variable will raise a `NoMethodError`. | ||
|
||
## Recommendation | ||
|
||
Ensure that the variable cannot be `nil` at the point hightligted by the alert. | ||
This can be achieved by using a safe navigation or adding a check for `nil`. | ||
|
||
Note: You do not need to explicitly initialize the variable, if you can make the program deal with the possible `nil` value. In particular, initializing the variable to `nil` will have no effect, as this is already the value of the variable. If `nil` is the only possibly default value, you need to handle the `nil` value instead of initializing the variable. | ||
|
||
## Examples | ||
|
||
In the following code, the call to `create_file` may fail and then the call `f.close` will raise a `NoMethodError` since `f` will be `nil` at that point. | ||
|
||
```ruby | ||
def dump(x) | ||
f = create_file | ||
f.puts(x) | ||
ensure | ||
f.close | ||
end | ||
``` | ||
|
||
We can fix this by using safe navigation: | ||
```ruby | ||
def dump(x) | ||
f = create_file | ||
f.puts(x) | ||
ensure | ||
f&.close | ||
end | ||
``` | ||
|
||
## References | ||
|
||
- https://www.rubyguides.com/: [Nil](https://www.rubyguides.com/2018/01/ruby-nil/) | ||
- https://ruby-doc.org/: [NoMethodError](https://ruby-doc.org/core-2.6.5/NoMethodError.html) | ||
|
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
14 changes: 14 additions & 0 deletions
14
ruby/ql/src/queries/variables/examples/UninitializedLocal.rb
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
def m | ||
puts "m" | ||
end | ||
|
||
def foo | ||
m # calls m above | ||
if false | ||
m = "0" | ||
m # reads local variable m | ||
else | ||
end | ||
m.strip # reads uninitialized local variable m, `nil`, and crashes | ||
m2 # undefined local variable or method 'm2' for main (NameError) | ||
end |
4 changes: 4 additions & 0 deletions
4
ruby/ql/test/query-tests/variables/UninitializedLocal/UninitializedLocal.expected
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
| UninitializedLocal.rb:12:3:12:3 | m | Local variable $@ may be used before it is initialized. | UninitializedLocal.rb:8:7:8:7 | m | m | | ||
| UninitializedLocal.rb:34:5:34:5 | b | Local variable $@ may be used before it is initialized. | UninitializedLocal.rb:27:9:27:9 | b | b | | ||
| UninitializedLocal.rb:34:23:34:23 | b | Local variable $@ may be used before it is initialized. | UninitializedLocal.rb:27:9:27:9 | b | b | | ||
| UninitializedLocal.rb:76:5:76:5 | i | Local variable $@ may be used before it is initialized. | UninitializedLocal.rb:73:9:73:9 | i | i | |
2 changes: 2 additions & 0 deletions
2
ruby/ql/test/query-tests/variables/UninitializedLocal/UninitializedLocal.qlref
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
query: queries/variables/UninitializedLocal.ql | ||
postprocess: utils/test/InlineExpectationsTestQuery.ql |
77 changes: 77 additions & 0 deletions
77
ruby/ql/test/query-tests/variables/UninitializedLocal/UninitializedLocal.rb
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
def m | ||
puts "m" | ||
end | ||
|
||
def foo | ||
m # calls m above | ||
if false | ||
m = "0" | ||
m # reads local variable m | ||
else | ||
end | ||
m.strip #$ Alert | ||
m2 # undefined local variable or method 'm2' for main (NameError) | ||
end | ||
|
||
def test_guards | ||
if (a = "3" && a) # OK - a is in a Boolean context | ||
a.strip | ||
end | ||
if (a = "3") && a # OK - a is assigned in the previous conjunct | ||
a.strip | ||
end | ||
if !(a = "3") or a # OK - a is assigned in the previous conjunct | ||
a.strip | ||
end | ||
if false | ||
b = "0" | ||
end | ||
b.nil? | ||
b || 0 # OK | ||
b&.strip # OK - safe navigation | ||
b.strip if b # OK | ||
b.close if b && !b.closed # OK | ||
b.blowup if b || !b.blownup #$ Alert | ||
|
||
if false | ||
c = "0" | ||
end | ||
unless c | ||
return | ||
end | ||
c.strip # OK - given above unless | ||
|
||
if false | ||
d = "0" | ||
end | ||
if (d.nil?) | ||
return | ||
end | ||
d.strip # OK - given above check | ||
|
||
if false | ||
e = "0" | ||
end | ||
unless (!e.nil?) | ||
return | ||
end | ||
e.strip # OK - given above unless | ||
end | ||
|
||
def test_loop | ||
begin | ||
if false | ||
a = 0 | ||
else | ||
set_a | ||
end | ||
end until a # OK | ||
a.strip # OK - given previous until | ||
end | ||
|
||
def test_for | ||
for i in ["foo", "bar"] # OK - since 0..10 cannot raise | ||
puts i.strip | ||
end | ||
i.strip #$ SPURIOUS: Alert | ||
end |
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the reason that this is OK is because
nil
is treated asfalse
in a Boolean context?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this loop will break once
a
is notnil
.