Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .claude/skills/new-rule/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ This skill provides sonar-java-specific guidelines for implementing new rules.
- Ensure your local rspec repository is up-to-date with the rule branch
- Use rule-api jar (check Maven local repository for available versions)
- Command: `java -jar <rule-api.jar> generate -branch rule/add-RSPEC-S{RULE_ID} -rule S{RULE_ID}`
- If the branch `rule/add-RSPEC-S{RULE_ID}` does not exist (e.g., for older rules already merged to master), fall back to: `java -jar <rule-api.jar> generate -rule S{RULE_ID}`
- This generates HTML and JSON files and updates the Sonar way profile automatically
- Generated files will be placed in:
- `sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S{RULE_ID}.html`
- `sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S{RULE_ID}.json`
- `sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json` (updated)
- `sonar-java-plugin/src/main/resources/profiles`

### 2. Tests
- Run JavaAgenticWayProfileTest before creating a PR
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package checks;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

public class InappropriateCastCheckSample {

interface Animal {}
interface Vehicle {}
interface Drawable {}

static class Dog implements Animal {}
static final class FinalDog implements Animal {}
static class Car implements Vehicle {}
static class Circle extends Shape implements Drawable {}
static class Shape {}
static abstract class AbstractShape {}

enum Color { RED, GREEN, BLUE }
enum Size { SMALL, MEDIUM, LARGE }

// Noncompliant: unrelated concrete classes
void unrelatedConcreteClasses(Dog dog, Car car, String str) {
Car c = (Car) dog; // Noncompliant {{"Dog" cannot be cast to "Car" without a risk of "ClassCastException".}}
Dog d = (Dog) car; // Noncompliant {{"Car" cannot be cast to "Dog" without a risk of "ClassCastException".}}
Dog d2 = (Dog) str; // Noncompliant {{"String" cannot be cast to "Dog" without a risk of "ClassCastException".}}
}

// Noncompliant: final class to unrelated interface
void finalClassToUnrelatedInterface(FinalDog finalDog, String str) {
Vehicle v = (Vehicle) finalDog; // Noncompliant {{"FinalDog" cannot be cast to "Vehicle" without a risk of "ClassCastException".}}
Drawable d = (Drawable) str; // Noncompliant {{"String" cannot be cast to "Drawable" without a risk of "ClassCastException".}}
}

// Noncompliant: interface to unrelated final class
void interfaceToUnrelatedFinalClass(Vehicle vehicle, Drawable drawable) {
FinalDog fd = (FinalDog) vehicle; // Noncompliant {{"Vehicle" cannot be cast to "FinalDog" without a risk of "ClassCastException".}}
String s = (String) drawable; // Noncompliant {{"Drawable" cannot be cast to "String" without a risk of "ClassCastException".}}
}

// Noncompliant: enums are implicitly final
void enumCasts(Color color, Size size) {
Size s = (Size) color; // Noncompliant {{"Color" cannot be cast to "Size" without a risk of "ClassCastException".}}
Drawable d = (Drawable) color; // Noncompliant {{"Color" cannot be cast to "Drawable" without a risk of "ClassCastException".}}
}

// Compliant: upcast (subtype to supertype)
void upcast(Circle circle, Dog dog) {
Shape s = (Shape) circle; // Compliant
Object o = (Object) dog; // Compliant
Animal a = (Animal) dog; // Compliant
}

// Compliant: downcast along hierarchy
void downcast(Shape shape, Animal animal) {
Circle c = (Circle) shape; // Compliant
Dog d = (Dog) animal; // Compliant
}

// Compliant: cast to/from Object
void objectCasts(Object obj, Dog dog) {
Dog d = (Dog) obj; // Compliant
Object o = (Object) dog; // Compliant
}

// Compliant: cast between interfaces
void interfaceCasts(Animal animal, Vehicle vehicle) {
Vehicle v = (Vehicle) animal; // Compliant
Animal a = (Animal) vehicle; // Compliant
}

// Compliant: non-final class to unrelated interface
void nonFinalClassToInterface(Dog dog, Shape shape) {
Vehicle v = (Vehicle) dog; // Compliant
Serializable s = (Serializable) shape; // Compliant
}

// Compliant: cast involving generics/wildcards
void genericCasts(List<?> wildcardList, List<Integer> intList) {
List<String> strList = (List<String>) wildcardList; // Compliant
ArrayList<Integer> al = (ArrayList<Integer>) intList; // Compliant
}

// Compliant: instanceof guard (still a valid downcast along hierarchy)
void instanceofGuard(Object obj) {
if (obj instanceof String) {
String s = (String) obj; // Compliant
}
}

// Compliant: cast to related interface (class implements interface)
void relatedInterfaceCast(Circle circle) {
Drawable d = (Drawable) circle; // Compliant
}

// Compliant: abstract class to interface
void abstractClassToInterface(AbstractShape abstractShape) {
Drawable d = (Drawable) abstractShape; // Compliant
}

// Compliant: cast involving type variables
<T> void typeVariableCast(T obj) {
String s = (String) obj; // Compliant
}

// Compliant: primitive casts
void primitiveCast(int i) {
long l = (long) i; // Compliant
double d = (double) i; // Compliant
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* SonarQube Java
* Copyright (C) SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* You can redistribute and/or modify this program under the terms of
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.checks;

import java.util.Collections;
import java.util.List;
import org.sonar.check.Rule;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.semantic.Type;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.TypeCastTree;

@Rule(key = "S1944")
public class InappropriateCastCheck extends IssuableSubscriptionVisitor {

@Override
public List<Tree.Kind> nodesToVisit() {
return Collections.singletonList(Tree.Kind.TYPE_CAST);
}

@Override
public void visitNode(Tree tree) {
TypeCastTree castTree = (TypeCastTree) tree;
Type sourceType = castTree.expression().symbolType().erasure();
Type targetType = castTree.type().symbolType().erasure();

if (shouldSkip(sourceType) || shouldSkip(targetType)) {
return;
}

if (sourceType.isSubtypeOf(targetType) || targetType.isSubtypeOf(sourceType)) {
return;
}

if (areNeitherInterfaces(sourceType, targetType) || areTypesFinalClassAndInterface(sourceType, targetType)) {
reportIssue(castTree.type(),
String.format("\"%s\" cannot be cast to \"%s\" without a risk of \"ClassCastException\".",
sourceType.name(), targetType.name()));
}
Comment on lines +49 to +53

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 Bug: Rule only matches casts that are compile-time errors

Every Noncompliant case detected by the two conditions in visitNode (areNeitherInterfaces = both are concrete classes; areTypesFinalClassAndInterface = a final class vs. an unrelated interface) is rejected by the Java compiler itself as "inconvertible types". I compiled the six Noncompliant samples with javac and all six fail to compile (Dog→Car, String→Dog, FinalDog→Vehicle, Vehicle→FinalDog, Color→Size, Color→Drawable). Since SonarJava only analyzes code that compiles, this rule can effectively never raise an issue on a real project, and the test sample file (InappropriateCastCheckSample.java) does not compile. A working S1944 must target casts that DO compile but fail at runtime (e.g. generic-argument casts via erasure, or interface→non-final-class casts where the concrete object is unrelated), not casts already forbidden by the compiler.

Was this helpful? React with 👍 / 👎

}

private static boolean shouldSkip(Type type) {
return type.isUnknown()
|| type.isPrimitive()
|| type.isVoid()
|| type.isNullType()
|| type.isTypeVar()
|| type.isArray()
|| type.is("java.lang.Object");
}

private static boolean areNeitherInterfaces(Type type1, Type type2) {
return !type1.symbol().isInterface() && !type2.symbol().isInterface();
}

private static boolean areTypesFinalClassAndInterface(Type type1, Type type2) {
return (type1.symbol().isInterface() && type2.symbol().isFinal())
|| (type2.symbol().isInterface() && type1.symbol().isFinal());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* SonarQube Java
* Copyright (C) SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* You can redistribute and/or modify this program under the terms of
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.checks;

import org.junit.jupiter.api.Test;
import org.sonar.java.checks.verifier.CheckVerifier;

import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath;

class InappropriateCastCheckTest {

@Test
void test() {
CheckVerifier.newVerifier()
.onFile(mainCodeSourcesPath("checks/InappropriateCastCheckSample.java"))
.withCheck(new InappropriateCastCheck())
.verifyIssues();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<h2>Why is this an issue?</h2>
<p>Inappropriate casts are errors that will lead to bugs as the members are accessed. This includes casts from one unrelated type to another, as well
as untested casts down an inheritance hierarchy.</p>
<h3>Noncompliant code example</h3>
<pre>
public class S1944 {

public static void main(String[] args) {
List&lt;String&gt; list = (List&lt;String&gt;) getAttributes(); // Noncompliant; List&lt;Integer&gt; return by getAttributes() is not be casted to List&lt;String&gt;
String s = list.get(0); // java.lang.ClassCastException will be raised here
}

private static List&lt;?&gt; getAttributes() {
List&lt;Integer&gt; result = new ArrayList&lt;&gt;();
result.add(0);
return result;
}

Comment on lines +4 to +18

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Quality: Implementation contradicts documented S1944 behavior

S1944.html documents the rule as flagging generic casts that fail at runtime, e.g. (List<String>) getAttributes() where the actual value is a List<Integer>. The implementation uses erasure() and explicitly treats exactly this scenario as Compliant (sample line 81: (List<String>) wildcardList). So the shipped documentation describes a case the code never detects, while the code detects only compile-error casts the docs never mention. Align the implementation with the documented intent (or update the HTML/JSON to reflect the actual, narrower behavior) before this leaves draft.

Was this helpful? React with 👍 / 👎

}
</pre>
<h3>Compliant solution</h3>
<pre>
public class S1944 {

public static void main(String[] args) {
List&lt;Integer&gt; list = (List&lt;Integer&gt;) getAttributes(); // Compliant
String s = String.valueOf(list.get(0));
}

private static List&lt;?&gt; getAttributes() {
List&lt;Integer&gt; result = new ArrayList&lt;&gt;();
result.add(0);
return result;
}

}
</pre>
<h2>Resources</h2>
<ul>
<li><a href="https://cmu-sei.github.io/secure-coding-standards/sei-cert-c-coding-standard/rules/expressions-exp/exp36-c">CERT, EXP36-C.</a> - Do not
cast pointers into more strictly aligned pointer types</li>
<li>CWE - <a href="https://cwe.mitre.org/data/definitions/588">CWE-588 - Attempt to Access Child of a Non-structure Pointer</a></li>
<li>CWE - <a href="https://cwe.mitre.org/data/definitions/704">CWE-704 - Incorrect Type Conversion or Cast</a></li>
</ul>

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"title": "Inappropriate casts should not be made",
"type": "CODE_SMELL",
"code": {
"impacts": {
"MAINTAINABILITY": "MEDIUM"
},
"attribute": "LOGICAL"
},
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "20min"
},
"tags": [
"cwe",
"cert",
"suspicious"
],
"defaultSeverity": "Major",
"ruleSpecification": "RSPEC-1944",
"sqKey": "S1944",
"scope": "All",
"securityStandards": {
"CERT": [
"EXP36-C."
],
"CWE": [
588,
704
]
},
"quickfix": "unknown"
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It looks like this was done using the wrong version of rule-api

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "Sonar agentic AI",
"ruleKeys": [
"S1944"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "Sonar way",
"ruleKeys": [
"S1944"
]
}
Empty file.
Empty file.
Loading