Skip to content

Commit

Permalink
Modify rule S6320: Adapt to LaYC format (#3158)
Browse files Browse the repository at this point in the history
  • Loading branch information
Swalkyn authored Sep 27, 2023
1 parent f0dc2a2 commit 6ac44b3
Showing 1 changed file with 109 additions and 23 deletions.
132 changes: 109 additions & 23 deletions rules/S6320/java/rule.adoc
Original file line number Diff line number Diff line change
@@ -1,50 +1,136 @@
This issue indicates that a cast operation will fail and throw a `ClassCastException`.
To fix it, make sure only references to objects with a compatible type can be cast.

== Why is this an issue?

A cast operation allows an object to be "converted" from one type to another.
The compiler raises an error if it can determine that the target type is incompatible with the declared type of the object, otherwise it accepts the cast.
However, depending on the actual runtime type of the object, a cast operation may fail at runtime.
When a cast operation fails, a `ClassCastException` is thrown.

=== What is the potential impact?

This type of exception is usually unexpected.
It causes the program to crash or puts it into an inconsistent state.
Therefore, this issue might impact the availability and reliability of your application, or even result in data loss.

== How to fix it

When an object is cast, the code makes assumptions about the type of the object.
A `ClassCastException` indicates that this assumption has been broken.
If the assumption is reasonable, then some prior logic should ensure that only objects of a compatible type can be cast.
You should try to identify the code responsible for these checks and fix it.

=== Code examples

==== Noncompliant code example

[source,java,diff-id=1,diff-type=noncompliant]
----
String hexString(Object o) {
return Integer.toHexString((Integer) o); // Noncompliant if hexString is called with a String for example
}
----

==== Compliant solution

A cast operation makes it possible to convert an object from one type to another one. The compiler raises an error when it can determine that the target type is incompatible with the declared type of the object. Other cases are accepted by the compiler. However, depending on the actual runtime type of the object, a cast operation may fail at runtime with a `ClassCastException` which can crash the program. This kind of failure happens when a piece of code makes an invalid assumption about the runtime type of an object.
One possible solution is to change `hexString` to only accept integers and adapt call sites.

The appropriate fix for those cast operations highly depends on the context: adding a condition before the cast or changing the runtime type of the object are some of the possible solutions.
[source,java,diff-id=1,diff-type=compliant]
----
String hexString(Integer i) {
return Integer.toHexString(i);
}
----

=== Noncompliant code example
Another one is to return a default value for types that are not `Integer`.
Here, the `if` statement with the condition relying on `instanceof` prevents references to objects with an incompatible type from making it to the cast operation.

[source,java]
[source,java,diff-id=1,diff-type=compliant]
----
List<String> list = new LinkedList<>();
if (someCondition) {
list = new ArrayList<>();
String hexString(Object o) {
if (o instanceof Integer) {
return Integer.toHexString((Integer) o);
}
return "0x0";
}
((LinkedList<String>) list).addLast("abc"); // Noncompliant, throws a ClassCastException when someCondition is true
----

=== Compliant solution
The solution to adopt depends on which part of the code should be responsible to handle non-Integer types.

=== Going the extra mile

[source,java]
Casting is considered an anti-pattern in object-oriented programming.
It is sometimes necessary, but there often is a better alternative.
Consider:

* Polymorphism
* The visitor design pattern
* Pattern matching

They come with some guarantees from the compiler, making your code more reliable.

==== Noncompliant code example

[source,java,diff-id=2,diff-type=noncompliant]
----
List<String> list = new LinkedList<>();
if (someCondition) {
list = new ArrayList<>();
int foo(Shape shape) {
if (shape instanceof Circle) {
Circle circle = (Circle) shape;
// Code for objects of type Circle
} else if (shape instanceof Square) {
Square square = (Square) shape;
// Code for objects of type Square
}
// Default
}
if (list instanceof LinkedList) {
((LinkedList<String>) list).addLast("abc");
----

==== Compliant solution

[source,java,diff-id=2,diff-type=compliant]
----
int foo(Shape shape) {
return shape.fooValue(...); // Code was moved into subclasses
}
----

or

[source,java]
[source,java,diff-id=2,diff-type=compliant]
----
List<String> list = new LinkedList<>();
if (someCondition) {
list = new ArrayList<>();
int foo(Shape shape) {
return shape.accept(new FooVisitor()); // Code was moved into FooVisitor
}
list.add("abc");
----

or

[source,java]
[source,java,diff-id=2,diff-type=compliant]
----
LinkedList<String> list = new LinkedList<>();
list.addLast("abc");
// Java 14+ required
int foo(Shape shape) {
if (shape instanceof Circle c) {
// Code for objects of type Circle
} else if (shape instanceof Square s) {
// Code for objects of type Square
}
}
----

== Resources

=== Documentation

* https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/ClassCastException.html[ClassCastException]

=== Articles & blog posts

* https://refactoring.guru/design-patterns/visitor[Refactoring Guru: Visitor]
* https://openjdk.org/jeps/394[JEP 394: Pattern Matching for `instanceof`]

=== Standards

* https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.16[JLS: Cast Expressions]
* https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.20.2[JLS: Type Comparison Operator `instanceof`]

0 comments on commit 6ac44b3

Please sign in to comment.