Skip to content
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

[CHALLENGE24][Samarium] Implement rules EC82, EC205, EC1245 #58

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added
- Add new rule EC1245 - Make non reassigned variables constants
- Add new rule EC205 - Force Lazy fetch types for JPA associations
- Add new rule EC1245 - Avoid energy consuming methods

### Changed

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
<google.re2j>1.7</google.re2j>

<!-- temporary version waiting for real automatic release in ecocode repository -->
<ecocode-rules-specifications.version>1.5.1</ecocode-rules-specifications.version>
<ecocode-rules-specifications.version>1.5.7-SNAPSHOT</ecocode-rules-specifications.version>

</properties>

Expand Down
27 changes: 8 additions & 19 deletions src/main/java/fr/greencodeinitiative/java/JavaCheckRegistrar.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,14 @@
*/
package fr.greencodeinitiative.java;

import java.util.Collections;
import java.util.List;

import fr.greencodeinitiative.java.checks.ArrayCopyCheck;
import fr.greencodeinitiative.java.checks.AvoidFullSQLRequest;
import fr.greencodeinitiative.java.checks.AvoidGettingSizeCollectionInLoop;
import fr.greencodeinitiative.java.checks.AvoidMultipleIfElseStatement;
import fr.greencodeinitiative.java.checks.AvoidRegexPatternNotStatic;
import fr.greencodeinitiative.java.checks.AvoidSQLRequestInLoop;
import fr.greencodeinitiative.java.checks.AvoidSetConstantInBatchUpdate;
import fr.greencodeinitiative.java.checks.AvoidSpringRepositoryCallInLoopOrStreamCheck;
import fr.greencodeinitiative.java.checks.AvoidStatementForDMLQueries;
import fr.greencodeinitiative.java.checks.AvoidUsageOfStaticCollections;
import fr.greencodeinitiative.java.checks.FreeResourcesOfAutoCloseableInterface;
import fr.greencodeinitiative.java.checks.IncrementCheck;
import fr.greencodeinitiative.java.checks.InitializeBufferWithAppropriateSize;
import fr.greencodeinitiative.java.checks.NoFunctionCallWhenDeclaringForLoop;
import fr.greencodeinitiative.java.checks.OptimizeReadFileExceptions;
import fr.greencodeinitiative.java.checks.*;
import org.sonar.plugins.java.api.CheckRegistrar;
import org.sonar.plugins.java.api.JavaCheck;
import org.sonarsource.api.sonarlint.SonarLintSide;

import java.util.Collections;
import java.util.List;

/**
* Provide the "checks" (implementations of rules) classes that are going be executed during
* source code analysis.
Expand All @@ -62,7 +48,10 @@ public class JavaCheckRegistrar implements CheckRegistrar {
InitializeBufferWithAppropriateSize.class,
AvoidSetConstantInBatchUpdate.class,
FreeResourcesOfAutoCloseableInterface.class,
AvoidMultipleIfElseStatement.class
AvoidMultipleIfElseStatement.class,
AvoidEnergyConsumingMethods.class,
MakeNonReassignedVariablesConstants.class,
ForceLazyFetchTypeForJPA.class
);

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/*
* ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs
* Copyright © 2023 Green Code Initiative (https://www.ecocode.io)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.greencodeinitiative.java.checks;

import org.sonar.check.Rule;
import org.sonar.plugins.java.api.InputFileScannerContext;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.tree.BaseTreeVisitor;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
import org.sonar.plugins.java.api.tree.MethodTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonarsource.analyzer.commons.annotations.DeprecatedRuleKey;

import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
* This rule aims to identify and flag methods in Java code that have high energy consumption
* due to the use of computationally expensive operations. The goal is to encourage developers
* to write more energy-efficient code by limiting the use of costly operations within a single method.
* <br>
* <br>
*
* The rule works by assigning a "cost" to specific expensive operations commonly recognized for their
* high computational demands. These operations include:
* <ul>
* <li>Reflection operations (e.g., {@code Class.forName()}, {@code Method.invoke()})</li>
* <li>Synchronization operations (e.g., {@code synchronized} blocks or methods)</li>
* <li>I/O operations (e.g., {@code FileInputStream}, {@code FileOutputStream}, {@code BufferedReader}, etc.)</li>
* <li>SQL operations (e.g., {@code Statement.execute()}, {@code ResultSet.next()})</li>
* <li>String operations (e.g., {@code String.replaceAll()}, {@code String.split()}, etc.)</li>
* <li>...</li>
* </ul>
* <br>
*
* For each occurrence of these operations within a method, a predefined number of points is assigned.
* If the total points for a method exceed a certain threshold (e.g., 5 points), the method is considered
* to be energy-inefficient. A warning is then issued to indicate that the method may have high energy
* consumption and suggest the developer to refactor or optimize the method.
* <br>
* <br>
*
* Example:
* <pre>
* {@code
* public class Example {
* public void exampleMethod() {
* String data = "example";
* data = data.replaceAll("e", "a"); // 1 point
* FileInputStream fis = new FileInputStream("file.txt"); // 1 point
* // Other operations
* }
* }
* }
* </pre>
* <br>
* In the above example, the `exampleMethod` uses `String.replaceAll()` and `FileInputStream`,
* each contributing 1 point to the total score. If additional expensive operations are added
* and the total score exceeds 5 points, the rule will flag this method.
* <br>
* <br>
*
* This rule helps developers identify potential performance bottlenecks and encourages
* the adoption of more efficient coding practices to reduce the energy footprint of their applications.
*
* @author Massil TAGUEMOUT - CGI FRANCE
*/
@Rule(key = "EC1245")
@DeprecatedRuleKey(repositoryKey = "greencodeinitiative-java", ruleKey = "EC1245")
public class AvoidEnergyConsumingMethods extends IssuableSubscriptionVisitor {

private static final int ENERGY_THRESHOLD = 5;
private static final Map<String, Integer> COSTLY_METHODS = new HashMap<>();

static {
// Reference costly methods in the map and their points
// Reflection methods
COSTLY_METHODS.put("java.lang.Class.forName", 1);
COSTLY_METHODS.put("java.lang.reflect.Method.invoke", 1);
COSTLY_METHODS.put("java.lang.reflect.Field.get", 1);
COSTLY_METHODS.put("java.lang.reflect.Field.set", 1);
COSTLY_METHODS.put("java.lang.reflect.Constructor.newInstance", 1);
COSTLY_METHODS.put("java.lang.Class.getMethods", 1);
COSTLY_METHODS.put("java.lang.Class.getDeclaredMethods", 1);
COSTLY_METHODS.put("java.lang.Class.getFields", 1);
COSTLY_METHODS.put("java.lang.Class.getDeclaredFields", 1);
COSTLY_METHODS.put("java.lang.Class.getConstructors", 1);
COSTLY_METHODS.put("java.lang.Class.getDeclaredConstructors", 1);

// String methods
COSTLY_METHODS.put("java.lang.String.concat", 1);
COSTLY_METHODS.put("java.lang.String.substring", 1);
COSTLY_METHODS.put("java.lang.String.replace", 1);
COSTLY_METHODS.put("java.lang.String.matches", 1);
COSTLY_METHODS.put("java.lang.String.split", 1);

// Collections
COSTLY_METHODS.put("java.util.Vector.add", 1);
COSTLY_METHODS.put("java.util.Vector.get", 1);
COSTLY_METHODS.put("java.util.Vector.remove", 1);
COSTLY_METHODS.put("java.util.Hashtable.put", 1);
COSTLY_METHODS.put("java.util.Hashtable.get", 1);
COSTLY_METHODS.put("java.util.Hashtable.remove", 1);
COSTLY_METHODS.put("java.util.Collections.synchronizedList", 1);
COSTLY_METHODS.put("java.util.Collections.synchronizedMap", 1);
COSTLY_METHODS.put("java.util.Collections.synchronizedSet", 1);
COSTLY_METHODS.put("java.util.Collections.synchronizedSortedMap", 1);
COSTLY_METHODS.put("java.util.Collections.synchronizedSortedSet", 1);

// File I/O
COSTLY_METHODS.put("java.io.FileInputStream.read", 1);
COSTLY_METHODS.put("java.io.FileOutputStream.write", 1);
COSTLY_METHODS.put("java.io.FileReader.read", 1);
COSTLY_METHODS.put("java.io.FileWriter.write", 1);
COSTLY_METHODS.put("java.io.RandomAccessFile.read", 1);
COSTLY_METHODS.put("java.io.RandomAccessFile.write", 1);
COSTLY_METHODS.put("java.nio.file.Files.readAllBytes", 1);
COSTLY_METHODS.put("java.nio.file.Files.write", 1);

// Network I/O
COSTLY_METHODS.put("java.net.Socket.getInputStream", 1);
COSTLY_METHODS.put("java.net.Socket.getOutputStream", 1);
COSTLY_METHODS.put("java.net.ServerSocket.accept", 1);
COSTLY_METHODS.put("java.net.HttpURLConnection.connect", 1);
COSTLY_METHODS.put("java.net.HttpURLConnection.getInputStream", 1);
COSTLY_METHODS.put("java.net.HttpURLConnection.getOutputStream", 1);

// JNI
COSTLY_METHODS.put("java.lang.System.loadLibrary", 1);
}

@Override
public List<Tree.Kind> nodesToVisit() {
return List.of(Tree.Kind.METHOD);
}

@Override
public void visitNode(Tree tree) {
if (tree.is(Tree.Kind.METHOD)) {
MethodTree methodTree = (MethodTree) tree;
int score = calculateMethodEnergyScore(methodTree);

if (score > ENERGY_THRESHOLD) {
reportIssue(methodTree.simpleName(),
"This method is considered energy-consuming with a score of " + score
+ " (maximum score of " + ENERGY_THRESHOLD + " recommended)");
}
}
}

private int calculateMethodEnergyScore(MethodTree methodTree) {
final AvoidEnergyConsumingMethodsVisitor visitor = new AvoidEnergyConsumingMethodsVisitor();
methodTree.accept(visitor);
return visitor.getScore();
}

@Override
public boolean scanWithoutParsing(InputFileScannerContext inputFileScannerContext) {
return super.scanWithoutParsing(inputFileScannerContext);
}


private static class AvoidEnergyConsumingMethodsVisitor extends BaseTreeVisitor {

private int score = 0;

@Override
public void visitMethodInvocation(MethodInvocationTree methodInvocationTree) {
Symbol symbol = methodInvocationTree.methodSymbol();
String fullyQualifiedName = symbol.owner().type().fullyQualifiedName() + "." + symbol.name();

score += COSTLY_METHODS.getOrDefault(fullyQualifiedName, 0);
super.visitMethodInvocation(methodInvocationTree);
}

public int getScore() {
return score;
}

}
}
Loading