Skip to content

Commit

Permalink
Modify rule S6466: Extend LaYC content (#3156)
Browse files Browse the repository at this point in the history
  • Loading branch information
anton-haubner-sonarsource authored Sep 27, 2023
1 parent 40852c3 commit f0dc2a2
Show file tree
Hide file tree
Showing 4 changed files with 220 additions and 28 deletions.
9 changes: 9 additions & 0 deletions rules/S6466/impact.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
=== What is the potential impact?

Issues of this type interrupt the normal execution of a program, causing it to
crash or putting it into an inconsistent state.
Therefore, this issue might impact the availability and reliability of your
application, or even result in data loss.

If the computation of an index value is tied to user input data, this issue can
potentially even be exploited by attackers to disrupt your application.
124 changes: 101 additions & 23 deletions rules/S6466/java/rule.adoc
Original file line number Diff line number Diff line change
@@ -1,58 +1,136 @@

An array index out of bounds exception is a bug class that occurs in Java when a program tries to access an array element that does not exist. This bug can cause a Java program to crash or behave unexpectedly, and it can also lead to security vulnerabilities. To fix an array index out of bounds exception in Java, you should always ensure that you are accessing array elements within the bounds of the array.
An array index out-of-bounds exception is a bug class that occurs in Java when a
program tries to access an array element that does not exist.
This bug can cause your program to crash or behave unexpectedly.
To fix an array index out of bounds exception in Java, you should always ensure
that you are accessing array elements within the bounds of the array.

// If you want to factorize the description uncomment the following line and create the file.
//include::../description.adoc[]

== Why is this an issue?

Array index out of bounds exception is the indication of a bug or a logical error in the code. It occurs when you try to access an array element with an index that is outside the valid range of the array. When it happens, it can cause the program to crash or put it into an inconsistent state. This can lead to serious consequences such as data loss. When the program is used for critical tasks such as financial transactions or medical records, the consequences could even be more severe. Therefore, it is important to fix array index out of bounds exception in Java to ensure that programs are reliable, secure, and function as intended.
//=== What is the potential impact?
An array index out-of-bounds exception indicates a bug or a logical error in the
code.

== How to fix it
//== How to fix it in FRAMEWORK NAME

To fix array index out of bounds exception in Java, you should always ensure that you are accessing array elements within the bounds of the array. Here are some best practices to follow:
In Java, arrays have a fixed size, and their elements are indexed starting from
`0`, counting up to the last index that is still smaller than the size.
When trying to access an array outside of this range, an
`ArrayIndexOutOfBoundsException` will be thrown and the operation will fail.

include::../impact.adoc[]

Always check the length of the array before accessing elements. This can be done using the `length` property of the array. For example, `myArray.length` returns the length of the `myArray` array. Also ensure that you are not accessing an array element located before position zero. This will ensure that you do not try to access elements that do not exist.
== How to fix it

Use loops to iterate over arrays instead of accessing elements directly. This can be done using the `for` loop or the `foreach` loop. For example, `for (int i = 0; i < myArray.length; i++)` iterates over the `myArray` array using an index variable `i`. The same goes for `for (Object o : myArray)`. If, inside the loop, you manipulate the index used to access the array, then make sure that the resulting index stays within the array bound. This will ensure that you do not accidentally access elements that do not exist.
=== Code examples

The following examples contain out-of-bounds accesses to arrays, resulting
in `ArrayIndexOutOfBounds` exceptions.
These situations can be avoided by carefully considering the range of valid
index values, or even better, by comparing indices against the size of a
sequence.

=== Code examples
In this first example, the array `arr` contains three elements.
Since the first element of a list has index `0`, the last valid index is `2`.
Therefore, an `ArrayIndexOutOfBoundsException` is thrown when accessing `arr` at
index `3`.

==== Noncompliant code example

[source,text,diff-id=1,diff-type=noncompliant]
[source,java,diff-id=1,diff-type=noncompliant]
----
void nonCompliant() {
int[] l = {1, 2, 3};
void fun() {
int[] arr = {1, 2, 3};
int x = l[-1]; // Noncompliant
int y = l[3]; // Noncompliant
int x = arr[3]; // Noncompliant: Valid indices are 0, 1 and 2
}
----

==== Compliant solution

[source,text,diff-id=1,diff-type=compliant]
[source,java,diff-id=1,diff-type=compliant]
----
void fun() {
int[] arr = {1, 2, 3};
int x = arr[0];
}
----
void compliant() {
int[] l = {1, 2, 3};

int x = l[0];
int y = l[2];
Accessing an array with its size as the index is never correct:

==== Noncompliant code example

[source,java,diff-id=2,diff-type=noncompliant]
----
void fun(int[] arr) {
System.out.println(arr[arr.length]); // Noncompliant: Indexing starts at 0, hence array.length will always be an invalid index.
}
----

==== Compliant solution

[source,java,diff-id=2,diff-type=compliant]
----
void compliant(int[] arr) {
// We can make sure arr is non-empty before trying to access its last element.
if (arr.length > 0) {
System.out.println(arr[arr.length - 1]);
} else {
System.out.println("Empty array!");
}
}
----

=== How does this work?

You should always ensure that you are accessing arrays using indices that are
within the bounds of the array.
Here are some best practices to follow:

Always compare indices against the length of an array using `if-else` statements
or other control flow constructs before accessing elements.
For this, you can use the `length` property of arrays.
For example, `arr.length` returns the length of the `arr` array.
Also, ensure that you are not accessing an array using negative indices.

Use loops to iterate over arrays instead of accessing elements directly.
This can be done using the `for` loop or the `foreach` loop.
For example, `for (int i = 0; i < arr.length; ++i)` iterates over the `arr`
array within its bounds using an index variable `i`.
The same goes for `for (Object o : arr)`.
If you manipulate the index used to access an array inside the loop, make sure
that the resulting index stays within bounds.

=== Pitfalls

The indices `0`, or `arr.length - 1` for the first and last element of an array
are not always valid!
Make sure the array in question is not empty before accessing these indices.

=== Going the extra mile

In many cases you can eliminate loops containing index-based accesses altogether
by applying the Java stream API to arrays using `java.util.Arrays.stream()`.
The API provides methods like `map()` or `filter()` that allow you to transform,
filter and perform many different operations on the elements of an array without
using indices.


== Resources

=== Documentation

* Java Documentation - https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/ArrayIndexOutOfBoundsException.html[ArrayIndexOutOfBoundsException]
* https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/ArrayIndexOutOfBoundsException.html[ArrayIndexOutOfBoundsException]
* https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Arrays.html#stream(T%5B%5D)[Javadoc for `Arrays.stream()`]
* https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/stream/Stream.html[Stream API]

=== Articles & blog posts

* A Reference Guide - https://www.baeldung.com/java-arrays-guide[Arrays in Java]

=== Standards

* https://docs.oracle.com/javase/specs/jls/se17/html/jls-10.html#jls-10.4[Array Access Specification]


ifdef::env-github,rspecator-view[]

Expand Down
2 changes: 1 addition & 1 deletion rules/S6466/python/metadata.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"title": "Accessing list elements should not trigger an IndexError",
"title": "Accessing sequence elements should not trigger an IndexError",
"type": "BUG",
"code": {
"impacts": {
Expand Down
113 changes: 109 additions & 4 deletions rules/S6466/python/rule.adoc
Original file line number Diff line number Diff line change
@@ -1,10 +1,41 @@
This rule raises an issue when trying to access a list or a tuple index that is out of bounds.

An `IndexError` is a bug class that occurs in Python when a program tries to
access a list, a tuple or other types of sequences at an index that does not
exist.
This bug can cause your program to crash or behave unexpectedly.
To fix an `IndexError`, you should always ensure you are accessing sequences
within their bounds.

== Why is this an issue?

Trying to access a list or a tuple index that is beyond the size of the list/tuple is probably a mistake and will result in an `IndexError`.
An `IndexError` indicates a bug or a logical error in the code.

In Python, lists and tuples have a certain length and their elements are indexed
starting from `0`, counting up to the last index that is still smaller than the
length.
When trying to access a list or tuple with an index outside of this range,
an `IndexError` will be raised and the operation will fail.

Negative indices are supported. When using a negative index, it will be
interpreted by computing the sum of the negative index and the list size.
The result is then used as the actual index for accessing the sequence.
Thus, this sum must be non-negative and fit into the aforementioned range.

include::../impact.adoc[]

== How to fix it

=== Code examples
=== Code examples

The following examples contain out-of-bounds accesses to sequences, resulting in
an `IndexError`.
These situations can be avoided by carefully considering the range of valid
index values, or even better, by comparing indices against the length of a
sequence.

In this first example, the list `ls` contains three elements.
Since the first element of a list has index `0`, the last valid index is `2`.
Therefore, an `IndexError` is raised when accessing `ls` at index `3`.

==== Noncompliant code example

Expand All @@ -26,11 +57,85 @@ def fun():
----

Accessing a list with its length as the index is never correct:

==== Noncompliant code example

[source,python,diff-id=2,diff-type=noncompliant]
----
def fun(ls: list[int]):
print(ls[len(ls)]) # Noncompliant: Indexing starts at 0, hence the list length will always be an invalid index.
----

==== Compliant solution

[source,python,diff-id=2,diff-type=compliant]
----
def fun(ls: list[int]):
# We can make sure ls is non-empty before trying to access its last element.
# Also, the index `len(ls) - 1` or just `-1` will correctly select the last
# element within bounds.
print("Empty list!" if not ls else ls[-1])
----

=== How does this work?

You should always ensure that you are accessing sequences using indices that are
within the bounds of the sequence.
Here are some best practices to follow:

Always compare indices against the length of a sequence using `if-else`
statements or other control flow constructs before accessing elements.
The length can be retrieved using the built-in `len(...)` function.
For example, `len(ls)` returns the length of the list `ls`.

Use loops to iterate over sequences instead of accessing elements directly.
For example, `for elem in ls:` iterates over the list `ls`.

=== Pitfalls

The indices `0`, `len(...) - 1`, or `-1` for the first and last element of a
sequence are not always valid!
Make sure the sequence in question is non-empty before accessing these
indices.

=== Going the extra mile

Built-in functions like `map()`, `filter()`, and `reduce()` present additional
ways to operate on sequences without using indices.

If you absolutely need to know the index of an element while iterating over a
sequence, you can use `enumerate()`. It provides you the indices and the
elements of a sequence during iteration, eliminating the need to manually
retrieve elements from the sequence using indices.

==== Noncompliant code example

[source,python,diff-id=3,diff-type=noncompliant]
----
for i in range(len(ls)):
elem = ls[i] # We can eliminate this access by index using enumerate.
foo(i, elem)
----

==== Compliant solution

[source,python,diff-id=3,diff-type=compliant]
----
for i, elem in enumerate(ls):
foo(i, elem)
----

== Resources

=== Documentation

* Python Documentation - https://docs.python.org/3/library/exceptions.html#IndexError[IndexError]
* https://docs.python.org/3/reference/expressions.html#subscriptions[Subscriptions]
* https://docs.python.org/3/library/exceptions.html#IndexError[IndexError]
* https://docs.python.org/3/library/functions.html#built-in-functions[Built-ins, including `map`, `filter`, `enumerate`, etc.]

ifdef::env-github,rspecator-view[]

Expand Down

0 comments on commit f0dc2a2

Please sign in to comment.