Skip to content

Commit 437aecf

Browse files
SONARJAVA-6084 Validation logic should be placed in constructor prologue when possible (#5446)
1 parent 387195b commit 437aecf

14 files changed

Lines changed: 562 additions & 18 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"ruleKey": "S8433",
3+
"hasTruePositives": true,
4+
"falseNegatives": 0,
5+
"falsePositives": 0
6+
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package checks;
2+
3+
import java.util.Objects;
4+
5+
class FlexibleConstructorBodyValidationCheckSample {
6+
7+
static class Coffee {
8+
Coffee() {}
9+
Coffee(int water, int milk) {}
10+
}
11+
12+
static class SmallCoffee extends Coffee {
13+
private String topping;
14+
private int maxVolume = 100;
15+
16+
public SmallCoffee(int water, int milk, String topping) {
17+
super(water, milk);
18+
int maxCupCoffee = 90;
19+
int totalVolume = water + milk;
20+
if (totalVolume > maxCupCoffee) { // Noncompliant {{Move this validation logic before the super() or this() call.}}
21+
//^[el=+3;ec=7]
22+
throw new IllegalArgumentException();
23+
}
24+
this.topping = topping;
25+
}
26+
27+
public SmallCoffee(int water, int milk) {
28+
int totalVolume = water + milk;
29+
int maxCupCoffee = 90;
30+
if (totalVolume > maxCupCoffee) { // Compliant: validation before super()
31+
throw new IllegalArgumentException();
32+
}
33+
maxCupCoffee++;
34+
super(water, milk);
35+
}
36+
37+
public SmallCoffee(int water) {
38+
super(water, 0);
39+
if (water > maxVolume) { // Compliant: Uses instance field
40+
throw new IllegalArgumentException();
41+
}
42+
}
43+
44+
public SmallCoffee() {
45+
super(0, 0); // Compliant: no validation
46+
}
47+
}
48+
49+
static class MediumCoffee extends Coffee {
50+
private String flavor;
51+
52+
public MediumCoffee(int water, int milk) {
53+
this.isValidFlavor("");
54+
super(water, milk);
55+
if (water + milk > MAX_SIZE) { // Noncompliant
56+
throw new IllegalArgumentException();
57+
}
58+
this.isValidFlavor("");
59+
this.flavor = "Vanilla";
60+
}
61+
62+
public MediumCoffee(int water) {
63+
if (water < 0) { // Compliant: throw before this()
64+
throw new IllegalArgumentException("Water cannot be negative");
65+
}
66+
this(water, 0);
67+
}
68+
69+
public MediumCoffee(String flavor) {
70+
this(100, 50);
71+
if (!isValidFlavor(flavor)) { // Compliant: validation uses instance method, cannot be moved
72+
throw new IllegalArgumentException();
73+
}
74+
this.flavor = flavor;
75+
}
76+
77+
private boolean isValidFlavor(String flavor) {
78+
return flavor != null && !flavor.isEmpty();
79+
}
80+
}
81+
82+
// Test using static members
83+
static class LargeCoffee extends Coffee {
84+
private String name;
85+
private static final int MAX_SIZE = 500;
86+
87+
public LargeCoffee(int water, int milk) {
88+
super(water, milk);
89+
if (water + milk > MAX_SIZE) { // Noncompliant
90+
throw new IllegalArgumentException();
91+
}
92+
}
93+
94+
public LargeCoffee(String name) {
95+
super(100, 100);
96+
this.name = name;
97+
if (!isValidName(name)) { // NonCompliant
98+
throw new IllegalArgumentException();
99+
}
100+
}
101+
102+
private static boolean isValidName(String name) {
103+
return name != null && !name.isEmpty();
104+
}
105+
}
106+
107+
// Test with different validation libraries
108+
static class GuavaCoffee extends Coffee {
109+
public GuavaCoffee(String name) {
110+
super(100, 50);
111+
com.google.common.base.Preconditions.checkNotNull(name); // Noncompliant {{Move this validation logic before the super() or this() call.}}
112+
}
113+
114+
public GuavaCoffee(String name) {
115+
com.google.common.base.Preconditions.checkNotNull(name); // Compliant
116+
super(100, 50);
117+
}
118+
}
119+
120+
static class SpringCoffee extends Coffee {
121+
public SpringCoffee(String name) {
122+
super(100, 50);
123+
org.springframework.util.Assert.notNull(name, "Name must not be null"); // Noncompliant {{Move this validation logic before the super() or this() call.}}
124+
}
125+
126+
public SpringCoffee(String name) {
127+
org.springframework.util.Assert.notNull(name, "Name must not be null"); // Compliant
128+
super(100, 50);
129+
}
130+
}
131+
132+
// Test with implicit super()
133+
static class ImplicitSuperCoffee extends Coffee {
134+
public ImplicitSuperCoffee(int water) {
135+
// Implicit super() call here
136+
if (water < 0) { // Noncompliant
137+
throw new IllegalArgumentException();
138+
}
139+
}
140+
}
141+
142+
// Test multiple validations
143+
static class MultiValidationCoffee extends Coffee {
144+
public MultiValidationCoffee(int water, int milk, String name) {
145+
super(water, milk);
146+
if (water < 0) { // Noncompliant
147+
throw new IllegalArgumentException("Invalid water");
148+
}
149+
if (milk < 0) { // Noncompliant
150+
throw new IllegalArgumentException("Invalid milk");
151+
}
152+
Objects.requireNonNull(name); // Noncompliant
153+
}
154+
}
155+
156+
// Test nested if statements
157+
static class NestedIfCoffee extends Coffee {
158+
public NestedIfCoffee(int water, int milk) {
159+
super(water, milk);
160+
if (water > 0) { // Noncompliant
161+
if (milk > 0) {
162+
if (water + milk > 200) {
163+
throw new IllegalArgumentException();
164+
}
165+
}
166+
}
167+
}
168+
}
169+
}

java-checks/src/main/java/org/sonar/java/checks/ClassWithOnlyStaticMethodsInstantiationCheck.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.sonar.java.checks;
1818

1919
import org.sonar.check.Rule;
20+
import org.sonar.java.model.ExpressionUtils;
2021
import org.sonar.java.model.JUtils;
2122
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
2223
import org.sonar.plugins.java.api.semantic.Symbol;
@@ -93,18 +94,13 @@ private static boolean superTypesHaveOnlyStaticMethods(Symbol.TypeSymbol newClas
9394
private static Collection<Symbol> filterMethodsAndFields(Collection<Symbol> symbols) {
9495
List<Symbol> filtered = new ArrayList<>();
9596
for (Symbol symbol : symbols) {
96-
if ((symbol.isVariableSymbol() && !isThisOrSuper(symbol)) || (symbol.isMethodSymbol() && !isConstructor(symbol))) {
97+
if ((symbol.isVariableSymbol() && !ExpressionUtils.isThisOrSuper(symbol.name())) || (symbol.isMethodSymbol() && !isConstructor(symbol))) {
9798
filtered.add(symbol);
9899
}
99100
}
100101
return filtered;
101102
}
102103

103-
private static boolean isThisOrSuper(Symbol symbol) {
104-
String name = symbol.name();
105-
return "this".equals(name) || "super".equals(name);
106-
}
107-
108104
private static boolean isConstructor(Symbol symbol) {
109105
return "<init>".equals(symbol.name());
110106
}

0 commit comments

Comments
 (0)