-
Notifications
You must be signed in to change notification settings - Fork 723
SONARJAVA-6685 Implement new rule S1944 #5842
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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())); | ||
| } | ||
| } | ||
|
|
||
| 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<String> list = (List<String>) getAttributes(); // Noncompliant; List<Integer> return by getAttributes() is not be casted to List<String> | ||
| String s = list.get(0); // java.lang.ClassCastException will be raised here | ||
| } | ||
|
|
||
| private static List<?> getAttributes() { | ||
| List<Integer> result = new ArrayList<>(); | ||
| result.add(0); | ||
| return result; | ||
| } | ||
|
|
||
|
Comment on lines
+4
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
| </pre> | ||
| <h3>Compliant solution</h3> | ||
| <pre> | ||
| public class S1944 { | ||
|
|
||
| public static void main(String[] args) { | ||
| List<Integer> list = (List<Integer>) getAttributes(); // Compliant | ||
| String s = String.valueOf(list.get(0)); | ||
| } | ||
|
|
||
| private static List<?> getAttributes() { | ||
| List<Integer> result = new ArrayList<>(); | ||
| 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" | ||
| } |
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" | ||
| ] | ||
| } |
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.
🚨 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 👍 / 👎