Skip to content

Commit 0571d2d

Browse files
authored
Merge pull request #160 from green-code-initiative/PR_70_DDC
[EC79] NullPointer exception in eco code java Sonar plugin
2 parents 9101316 + ab589ae commit 0571d2d

8 files changed

Lines changed: 267 additions & 33 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
### Changed
1313

14+
- [#69](https://github.com/green-code-initiative/creedengo-java/issues/69) correction of NullPointer in GCI79 rule + technical refactoring of GCI79
1415
- update integration tests system to use the new component "creedengo-integration-test"
1516

1617
### Deleted

IA_description.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
This file contains the technical description of the project
2+
3+
Global description
4+
---
5+
This project is a SonarQube plugin project and it is named "creedengo-java".
6+
This plugin is for Java project analysis.
7+
When this plugin is installed inside SonarQube, it adds some new rules for Java language analysis.
8+
The new rules can be added to an existing Java quality profile or to a new Java quality profile.
9+
A Sonarqube quality profile must be associated to only one programming language.
10+
A quality profile can be the default quality profile for one language (here Java language), thus
11+
all future Java project analysis will be done by default with this quality profile.
12+
If a quality profile is not the default, a specific project (already analysed) can be associated with this
13+
quality profile.
14+
15+
Technical description
16+
---
17+
Important elements of the plugin :
18+
- the plugin contains a list of rule implementations
19+
- each rule has a unique rule id
20+
- each rule is implemented by one class
21+
- there are several tests for each implementation rule class with several ressource files containing compliant code or / and non compliant code
22+
23+
The implementation code is in src/main/java.
24+
25+
Sonarqube analysis are based on the navigation inside the code to detect bad practices and to raise an issue when detected.
26+
The code navigation is done through the AST principle.
27+
28+
Implementation Structure
29+
---
30+
- rule implementations : inside the package org.greencodeinitiative.creedengo.java.checks
31+
- each implementation class has the same code template :
32+
- "Rule" annotation : to give the rule id
33+
- the rule id is previously defined in another maven component named "creedengo-rules-specifications"
34+
- to enable a rule, the rule id must be added in the resources file named "creedengo_way_profile.json"
35+
- extends a SonarQube API class "IssuableSubscriptionVisitor"
36+
- "initialize" method (forom super-class) to declare for which AST node type, the analysis will deeply analyse the code.
37+
- each "registerSyntaxNodeConsumer" method call implies that the node will be deeply analyzed.
38+
- For each, a new private method is given to implement the deeply analysis and raise an issue if needed.
39+
- to have a plugin working with activated rules, the Sonarqube plugin development guidelines is followed
40+
- "JavaPlugin" : enable 2 following extensions
41+
- "JavaRuleRepository" : extension containing the definition of the plugin and the list of implemented rule classes
42+
- "JavaCreedengoWayProfile" : extension containing the definition a the quality profile (and rules activated inside) created with the plugin installation
43+
44+
Unit Tests Implementation Structure
45+
---
46+
Inside the "test/java" directory, there are unit tests for each class of the plugin.
47+
The package "org.greencodeinitiative.creedengo.java.checks" contains one unit test class for each rule implementation class
48+
49+
Each unit test class checking rule implementation class has the same template of code :
50+
- at least one test method using the "CheckVerifier" class to check and simulate a SonarQube analysis
51+
- usage of "CheckVerifier.verify" to check if there is some issues raised or not
52+
- usage of "CheckVerifier.verifyNoIssues" to check there is no issues raised
53+
- each call to "CheckVerifier" needs a test resource file : this one is the resource code file for the simulated analysis
54+
55+
each test resource file is in the "src/test/files" directory (or sub-directories).
56+
each test resource file contains compliuant code or / and non compliant code.
57+
If there is non compliant code on which Sonarqube analysis should raise an issue, the line with the issue has a comment at the end of the line with the following template :
58+
"// Noncompliant {{ERROR_MESSAGE_TO_DISPLAY}}"
59+
- the "ERROR_MESSAGE_TO_DISPLAY" in the previous template is replaced by the real error message.
60+
- this comment gives the information to "CheckVerifier", the simulation tool, that an error is expected at this line
61+
62+
Integration Tests Implementation Structure
63+
---
64+
Inside the "it/java" directory, there are integration tests for each class of the plugin.
65+
The package "org.greencodeinitiative.creedengo.java.integration.tests" contains one unit test class with one method for each rule.
66+
67+
The integration test class extends the common class "GCIRulesBase" to use the system to initialize integration test.
68+
69+
Integration test system process exists to check in a local and real environment that all implemented rules do the raises expected issues.
70+
71+
The "src/it/test-projects" directory contains a real project to analyse.
72+
73+
Here is the process
74+
- build the plugin project
75+
- download and launch a specific sonarqube version in local machine
76+
- install the built plugin inside sonarqube
77+
- create a specific default quality profile with all rules of the plugin installed
78+
- launch the analysis of the test-project
79+
- send analysis result to local sonarqube
80+
- check the result of the analysis in front of expected results describe inside each test method
81+
82+
Each test method describe different elements to check inside the result analysis :
83+
- the relative path of each resource test file inside de test-project
84+
- the complete rule id containing 2 parts : the plugin id ("creedengo-java") and the rule id (ex : "GCI2")
85+
- the rule error message
86+
- the lines where errors shoudl be raised : there are 2 arrays with the same size representing the start line and the end line of each issue raised
87+
88+
Each test method ends with a call of a common method with all these parameters input.

IA_rule.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
Rule Implementation Plan
2+
---
3+
4+
# Rule Implementation Methodology
5+
6+
Use the TDD (Test-Driven Development) methodology to implement a rule in an efficient and structured way.
7+
8+
# Rule Implementation Process
9+
10+
## STEP 1: Define unit test resources + technical shell of the rule
11+
- Define test resources for the rule to be implemented with all possible cases:
12+
- Compliant code
13+
- Non-compliant code
14+
- Code with elements that should not be analyzed
15+
- Location of test resource files:
16+
- Files for unit tests: in "src/test/files" or subdirectories
17+
- Files for integration tests: in "src/it/test-projects" or subdirectories
18+
- Create the rule implementation class in the package "org.greencodeinitiative.creedengo.java.checks"
19+
- Add the "Rule" annotation with the correct rule id (the rule is previously defined in another maven component named "creedengo-rules-specifications")
20+
- Extend the "IssuableSubscriptionVisitor" class
21+
- Override the "nodesToVisit", "visitNode" and (optionally) "leaveNode" methods, which will be used respectively to declare the AST node types to analyze in depth and to implement the analysis logic, but leave their bodies empty for now
22+
- Create the rule unit test class in the package "org.greencodeinitiative.creedengo.java.checks"
23+
- Verify that unit tests fail with non-compliant test resources
24+
25+
## STEP 2: Implement the rule + unit test validation
26+
- Implement the rule in the implementation class created in step 1
27+
- Implement the "nodesToVisit" method to declare the AST node types to analyze in depth and implement the "visitNode" and, if needed, "leaveNode" methods to perform the analysis on those nodes
28+
- Implement private methods to analyze in depth the declared AST nodes and raise issues if needed
29+
- Verify that unit tests pass with both compliant and non-compliant test resources, and call them from "visitNode"/"leaveNode"
30+
31+
## STEP 3: Implement integration tests
32+
- Add a test method in the integration class "GCIRulesIT" for the implemented rule using the same pattern as other existing test methods
33+
- Verify that integration tests pass with both compliant and non-compliant test resources

src/it/java/org/greencodeinitiative/creedengo/java/integration/tests/GCIRulesIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ void testGCI76_good() {
408408
void testGCI79() {
409409
String filePath = "src/main/java/org/greencodeinitiative/creedengo/java/checks/FreeResourcesOfAutoCloseableInterface.java";
410410
String ruleId = "creedengo-java:GCI79";
411-
String ruleMsg = "try-with-resources Statement needs to be implemented for any object that implements the AutoClosable interface.";
411+
String ruleMsg = "try-with-resources Statement needs to be implemented for any object that implements the AutoCloseable interface.";
412412
int[] startLines = new int[]{23};
413413
int[] endLines = new int[]{36};
414414

src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/FreeResourcesOfAutoCloseableInterface.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,25 @@ public void foo2() throws IOException {
3535
}
3636
}
3737
}
38+
39+
/**
40+
* The first method adds a "try" in the stack used to follow if the code is in a try
41+
*/
42+
public void callingMethodWithTheTry() throws IOException {
43+
try { // Compliant
44+
calledMethodWithoutTry();
45+
} finally {
46+
// Empty block of code
47+
}
48+
}
49+
50+
/**
51+
* The "try" should have been popped from the stack before entering here
52+
*/
53+
private void calledMethodWithoutTry() throws IOException {
54+
FileWriter myWriter = new FileWriter("somefilepath");
55+
myWriter.write("something");
56+
myWriter.flush();
57+
myWriter.close();
58+
}
3859
}

src/main/java/org/greencodeinitiative/creedengo/java/checks/FreeResourcesOfAutoCloseableInterface.java

Lines changed: 98 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,77 +17,146 @@
1717
*/
1818
package org.greencodeinitiative.creedengo.java.checks;
1919

20+
import java.util.ArrayDeque;
2021
import java.util.ArrayList;
21-
import java.util.Arrays;
2222
import java.util.Deque;
23-
import java.util.LinkedList;
2423
import java.util.List;
2524

25+
import javax.annotation.Nonnull;
2626
import javax.annotation.ParametersAreNonnullByDefault;
2727

2828
import org.sonar.check.Rule;
2929
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
3030
import org.sonar.plugins.java.api.JavaFileScannerContext;
31-
import org.sonar.plugins.java.api.JavaVersion;
3231
import org.sonar.plugins.java.api.tree.NewClassTree;
3332
import org.sonar.plugins.java.api.tree.Tree;
3433
import org.sonar.plugins.java.api.tree.TryStatementTree;
3534
import org.sonarsource.analyzer.commons.annotations.DeprecatedRuleKey;
3635

37-
36+
/**
37+
* This rule checks that objects implementing AutoCloseable interface are properly managed
38+
* using try-with-resources statement instead of try-finally blocks.
39+
* <p>
40+
* Try-with-resources ensures proper resource management and reduces the risk of resource leaks.
41+
* It also reduces boilerplate code and improves code readability.
42+
* <p>
43+
* From an environmental perspective, proper resource management prevents resource leaks
44+
* which can lead to increased memory consumption and unnecessary CPU cycles.
45+
*
46+
* @see <a href="https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html">Try-with-resources</a>
47+
*/
3848
@Rule(key = "GCI79")
3949
@DeprecatedRuleKey(repositoryKey = "ecocode-java", ruleKey = "EC79")
4050
@DeprecatedRuleKey(repositoryKey = "greencodeinitiative-java", ruleKey = "S79")
4151
public class FreeResourcesOfAutoCloseableInterface extends IssuableSubscriptionVisitor {
42-
private final Deque<TryStatementTree> withinTry = new LinkedList<>();
43-
private final Deque<List<Tree>> toReport = new LinkedList<>();
52+
53+
/**
54+
* Stack to track nested try statements while traversing the AST
55+
*/
56+
private final Deque<TryStatementContext> tryStack = new ArrayDeque<>();
4457

4558
private static final String JAVA_LANG_AUTOCLOSEABLE = "java.lang.AutoCloseable";
46-
protected static final String MESSAGE_RULE = "try-with-resources Statement needs to be implemented for any object that implements the AutoClosable interface.";
59+
protected static final String MESSAGE_RULE = "try-with-resources Statement needs to be implemented for any object that implements the AutoCloseable interface.";
4760

4861
@Override
4962
@ParametersAreNonnullByDefault
5063
public void leaveFile(JavaFileScannerContext context) {
51-
withinTry.clear();
52-
toReport.clear();
64+
tryStack.clear();
5365
}
5466

5567
@Override
68+
@Nonnull
5669
public List<Tree.Kind> nodesToVisit() {
57-
return Arrays.asList(Tree.Kind.TRY_STATEMENT, Tree.Kind.NEW_CLASS);
70+
return List.of(Tree.Kind.TRY_STATEMENT, Tree.Kind.NEW_CLASS);
5871
}
5972

6073
@Override
61-
public void visitNode(Tree tree) {
74+
public void visitNode(@Nonnull Tree tree) {
6275
if (tree.is(Tree.Kind.TRY_STATEMENT)) {
63-
withinTry.push((TryStatementTree) tree);
64-
if (withinTry.size() != toReport.size()) {
65-
toReport.push(new ArrayList<>());
66-
}
67-
}
68-
if (tree.is(Tree.Kind.NEW_CLASS) && ((NewClassTree) tree).symbolType().isSubtypeOf(JAVA_LANG_AUTOCLOSEABLE) && withinStandardTryWithFinally()) {
69-
assert toReport.peek() != null;
70-
toReport.peek().add(tree);
76+
handleTryStatement((TryStatementTree) tree);
77+
} else if (tree.is(Tree.Kind.NEW_CLASS)) {
78+
handleNewClass((NewClassTree) tree);
7179
}
7280
}
7381

7482
@Override
75-
public void leaveNode(Tree tree) {
83+
public void leaveNode(@Nonnull Tree tree) {
7684
if (tree.is(Tree.Kind.TRY_STATEMENT)) {
77-
List<Tree> secondaryTrees = toReport.pop();
78-
if (!secondaryTrees.isEmpty()) {
79-
reportIssue(tree, MESSAGE_RULE);
85+
leaveTryStatement();
86+
}
87+
}
88+
89+
/**
90+
* Handle entering a try statement by pushing it onto the stack
91+
*/
92+
private void handleTryStatement(@Nonnull TryStatementTree tryStatement) {
93+
tryStack.push(new TryStatementContext(tryStatement));
94+
}
95+
96+
/**
97+
* Handle leaving a try statement by popping it from the stack and reporting issues if needed
98+
*/
99+
private void leaveTryStatement() {
100+
if (!tryStack.isEmpty()) {
101+
TryStatementContext context = tryStack.pop();
102+
if (!context.autoCloseableInstances.isEmpty()) {
103+
reportIssue(context.tryStatement, MESSAGE_RULE);
104+
}
105+
}
106+
}
107+
108+
/**
109+
* Handle new class instantiation to detect AutoCloseable objects
110+
* that are created inside a try-finally block (without try-with-resources)
111+
*/
112+
private void handleNewClass(@Nonnull NewClassTree newClass) {
113+
// Check if the new instance is an AutoCloseable
114+
if (!newClass.symbolType().isSubtypeOf(JAVA_LANG_AUTOCLOSEABLE)) {
115+
return;
116+
}
117+
118+
// Check if we are inside a non-compliant try statement
119+
if (isInNonCompliantTry()) {
120+
TryStatementContext context = tryStack.peek();
121+
if (context != null) {
122+
context.autoCloseableInstances.add(newClass);
80123
}
81124
}
82125
}
83126

84-
private boolean withinStandardTryWithFinally() {
85-
if (withinTry.isEmpty() || !withinTry.peek().resourceList().isEmpty()) return false;
86-
assert withinTry.peek() != null;
87-
return withinTry.peek().finallyBlock() != null;
127+
/**
128+
* Check if we are currently inside a try statement that:
129+
* - Does NOT use try-with-resources (no resource list)
130+
* - Has a finally block (indicating manual resource management)
131+
*
132+
* @return true if inside a non-compliant try statement
133+
*/
134+
private boolean isInNonCompliantTry() {
135+
if (tryStack.isEmpty()) {
136+
return false;
137+
}
138+
139+
TryStatementTree currentTry = tryStack.peek().tryStatement;
140+
141+
// If try-with-resources is already used, it's compliant
142+
if (!currentTry.resourceList().isEmpty()) {
143+
return false;
144+
}
145+
146+
// If there's a finally block, it suggests manual resource management
147+
return currentTry.finallyBlock() != null;
88148
}
89149

90-
public boolean isCompatibleWithJavaVersion(JavaVersion version) {
91-
return version.isJava7Compatible();
150+
/**
151+
* Context class to track information about a try statement during AST traversal
152+
*/
153+
private static class TryStatementContext {
154+
final TryStatementTree tryStatement;
155+
final List<Tree> autoCloseableInstances;
156+
157+
TryStatementContext(@Nonnull TryStatementTree tryStatement) {
158+
this.tryStatement = tryStatement;
159+
this.autoCloseableInstances = new ArrayList<>();
160+
}
92161
}
93162
}

src/test/files/FreeResourcesOfAutoCloseableInterface.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@ class FreeResourcesOfAutoCloseableInterface {
2727
public void foo1() {
2828
String fileName = "./FreeResourcesOfAutoCloseableInterface.java";
2929
try (FileReader fr = new FileReader(fileName);
30-
BufferedReader br = new BufferedReader(fr)) {
30+
BufferedReader br = new BufferedReader(fr)) { // Compliant
3131
} catch (IOException e) {
3232
System.err.println(e.getMessage());
3333
}
3434
}
3535

3636
public void foo2() {
3737
String fileName = "./FreeResourcesOfAutoCloseableInterface.java";
38-
try { // Noncompliant
38+
try { // Noncompliant {{try-with-resources Statement needs to be implemented for any object that implements the AutoCloseable interface.}}
3939
FileReader fr = new FileReader(fileName);
4040
BufferedReader br = new BufferedReader(fr);
4141
System.out.printl(br.readLine());
@@ -50,4 +50,25 @@ public void foo2() {
5050
}
5151
}
5252
}
53+
54+
/**
55+
* The first method adds a "try" in the stack used to follow if the code is in a try
56+
*/
57+
public void callingMethodWithTheTry() throws IOException {
58+
try { // Compliant
59+
calledMethodWithoutTry();
60+
} finally {
61+
// Empty block of code
62+
}
63+
}
64+
65+
/**
66+
* The "try" should have been popped from the stack before entering here
67+
*/
68+
private void calledMethodWithoutTry() throws IOException {
69+
FileWriter myWriter = new FileWriter("somefilepath");
70+
myWriter.write("something");
71+
myWriter.flush();
72+
myWriter.close();
73+
}
5374
}

0 commit comments

Comments
 (0)