From 6032969025db3bd5e220eb15f2e4af23c9bee9e7 Mon Sep 17 00:00:00 2001 From: Denielig Date: Thu, 30 May 2024 09:30:54 +0200 Subject: [PATCH 01/32] feat: add test files --- .../UseEveryColumnQueriedCompliant1.java | 50 ++++++++++++++ .../UseEveryColumnQueriedCompliant2.java | 51 ++++++++++++++ .../UseEveryColumnQueriedCompliant3.java | 50 ++++++++++++++ .../UseEveryColumnQueriedCompliant4.java | 50 ++++++++++++++ .../UseEveryColumnQueriedCompliant5.java | 66 +++++++++++++++++++ .../UseEveryColumnQueriedCompliant6.java | 53 +++++++++++++++ .../UseEveryColumnQueriedNonCompliant1.java | 49 ++++++++++++++ .../UseEveryColumnQueriedNonCompliant2.java | 50 ++++++++++++++ .../UseEveryColumnQueriedNonCompliant3.java | 49 ++++++++++++++ .../UseEveryColumnQueriedNonCompliant4.java | 49 ++++++++++++++ .../UseEveryColumnQueriedNonCompliant5.java | 64 ++++++++++++++++++ .../UseEveryColumnQueriedNonCompliant6.java | 52 +++++++++++++++ 12 files changed, 633 insertions(+) create mode 100644 src/test/files/UseEveryColumnQueriedCompliant1.java create mode 100644 src/test/files/UseEveryColumnQueriedCompliant2.java create mode 100644 src/test/files/UseEveryColumnQueriedCompliant3.java create mode 100644 src/test/files/UseEveryColumnQueriedCompliant4.java create mode 100644 src/test/files/UseEveryColumnQueriedCompliant5.java create mode 100644 src/test/files/UseEveryColumnQueriedCompliant6.java create mode 100644 src/test/files/UseEveryColumnQueriedNonCompliant1.java create mode 100644 src/test/files/UseEveryColumnQueriedNonCompliant2.java create mode 100644 src/test/files/UseEveryColumnQueriedNonCompliant3.java create mode 100644 src/test/files/UseEveryColumnQueriedNonCompliant4.java create mode 100644 src/test/files/UseEveryColumnQueriedNonCompliant5.java create mode 100644 src/test/files/UseEveryColumnQueriedNonCompliant6.java diff --git a/src/test/files/UseEveryColumnQueriedCompliant1.java b/src/test/files/UseEveryColumnQueriedCompliant1.java new file mode 100644 index 0000000..e81a11c --- /dev/null +++ b/src/test/files/UseEveryColumnQueriedCompliant1.java @@ -0,0 +1,50 @@ +/* + * 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 . + */ +package fr.greencodeinitiative.java.checks; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +public class UseEveryColumnQueriedCompliant1 { + + private static final String DB_URL = "jdbc:mysql://localhost/TEST"; + private static final String USER = "guest"; + private static final String PASS = "guest123"; + private static final String QUERY = "SELECT id, first, last, age FROM Registration"; + + public void callJdbc() { + + try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(QUERY);) { + while (rs.next()) { + // Display values + System.out.print("ID: " + rs.getInt("id")); + System.out.print(", Age: " + rs.getInt("age")); + System.out.print(", First: " + rs.getString("first")); + System.out.println(", Last: " + rs.getString("last")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + } +} diff --git a/src/test/files/UseEveryColumnQueriedCompliant2.java b/src/test/files/UseEveryColumnQueriedCompliant2.java new file mode 100644 index 0000000..3b45c2f --- /dev/null +++ b/src/test/files/UseEveryColumnQueriedCompliant2.java @@ -0,0 +1,51 @@ +/* + * 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 . + */ +package fr.greencodeinitiative.java.checks; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +public class UseEveryColumnQueriedCompliant2 { + + private static final String DB_URL = "jdbc:mysql://localhost/TEST"; + private static final String USER = "guest"; + private static final String PASS = "guest123"; + + public void callJdbc() { + + try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT id, first, last, age FROM Registration");) { + while (rs.next()) { + // Display values + System.out.print("ID: " + rs.getInt("id")); + System.out.print(", Age: " + rs.getInt("age")); + System.out.print(", First: " + rs.getString("first")); + System.out.println(", Last: " + rs.getString("last")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + } + + +} diff --git a/src/test/files/UseEveryColumnQueriedCompliant3.java b/src/test/files/UseEveryColumnQueriedCompliant3.java new file mode 100644 index 0000000..a6f0fe7 --- /dev/null +++ b/src/test/files/UseEveryColumnQueriedCompliant3.java @@ -0,0 +1,50 @@ +/* + * 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 . + */ +package fr.greencodeinitiative.java.checks; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +public class UseEveryColumnQueriedCompliant3 { + + private static final String DB_URL = "jdbc:mysql://localhost/TEST"; + private static final String USER = "guest"; + private static final String PASS = "guest123"; + private static final String QUERY = "SELECT id, first, last, age FROM Registration"; + + public void callJdbc() { + + try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(QUERY);) { + while (rs.next()) { + // Display values + System.out.print("ID: " + rs.getInt("1")); + System.out.print(", Age: " + rs.getInt("4")); + System.out.print(", First: " + rs.getString("2")); + System.out.println(", Last: " + rs.getString("3")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + } +} diff --git a/src/test/files/UseEveryColumnQueriedCompliant4.java b/src/test/files/UseEveryColumnQueriedCompliant4.java new file mode 100644 index 0000000..350b23c --- /dev/null +++ b/src/test/files/UseEveryColumnQueriedCompliant4.java @@ -0,0 +1,50 @@ +/* + * 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 . + */ +package fr.greencodeinitiative.java.checks; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +public class UseEveryColumnQueriedCompliant4 { + + private static final String DB_URL = "jdbc:mysql://localhost/TEST"; + private static final String USER = "guest"; + private static final String PASS = "guest123"; + private static final String QUERY = "SELECT id, first, last, age FROM Registration"; + + public void callJdbc() { + + try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(QUERY);) { + while (rs.next()) { + // Display values + System.out.print("ID: " + rs.getInt("id")); + System.out.print(", Age: " + rs.getInt(4)); + System.out.print(", First: " + rs.getString(2)); + System.out.println(", Last: " + rs.getString("last")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + } +} diff --git a/src/test/files/UseEveryColumnQueriedCompliant5.java b/src/test/files/UseEveryColumnQueriedCompliant5.java new file mode 100644 index 0000000..7fedb2a --- /dev/null +++ b/src/test/files/UseEveryColumnQueriedCompliant5.java @@ -0,0 +1,66 @@ +/* + * 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 . + */ +package fr.greencodeinitiative.java.checks; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +public class UseEveryColumnQueriedCompliant5 { + + private static final String DB_URL = "jdbc:mysql://localhost/TEST"; + private static final String USER = "guest"; + private static final String PASS = "guest123"; + private static final String QUERY = "SELECT id, first, last, age FROM Registration"; + private static final String QUERY2 = "SELECT id, first, last, age FROM Registration2"; + + + public void callJdbc() { + + try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); + Statement stmt = conn.createStatement(); + ) { + + ResultSet rs = stmt.executeQuery(QUERY); + while (rs.next()) { + // Display values + System.out.print("Age: " + rs.getInt("age")); + System.out.print("ID: " + rs.getInt("id")); + System.out.print(", First: " + rs.getString("first")); + System.out.println(", Last: " + rs.getString("last")); + } + rs = stmt.executeQuery(QUERY2); + + + while (rs.next()) { + // Display values + System.out.print("Age: " + rs.getInt("age")); + System.out.print("ID: " + rs.getInt("id")); + System.out.print(", First: " + rs.getString("first")); + System.out.println(", Last: " + rs.getString("last")); + } + + + } catch (SQLException e) { + e.printStackTrace(); + } + + } +} diff --git a/src/test/files/UseEveryColumnQueriedCompliant6.java b/src/test/files/UseEveryColumnQueriedCompliant6.java new file mode 100644 index 0000000..a3b28e8 --- /dev/null +++ b/src/test/files/UseEveryColumnQueriedCompliant6.java @@ -0,0 +1,53 @@ +/* + * 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 . + */ +package fr.greencodeinitiative.java.checks; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +public class UseEveryColumnQueriedCompliant6 { + + private static final String DB_URL = "jdbc:mysql://localhost/TEST"; + private static final String USER = "guest"; + private static final String PASS = "guest123"; + + public void callJdbc() { + + try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT id, first, last, age FROM Registration");) { + extractGet(rs); + } catch (SQLException e) { + e.printStackTrace(); + } + + } + + private void extractGet(ResultSet rs) throws SQLException { + while (rs.next()) { + // Display values + System.out.print("ID: " + rs.getInt("id")); + System.out.print(", Age: " + rs.getInt("age")); + System.out.print(", First: " + rs.getString("first")); + System.out.println(", Last: " + rs.getString("last")); + } + } +} diff --git a/src/test/files/UseEveryColumnQueriedNonCompliant1.java b/src/test/files/UseEveryColumnQueriedNonCompliant1.java new file mode 100644 index 0000000..0daf184 --- /dev/null +++ b/src/test/files/UseEveryColumnQueriedNonCompliant1.java @@ -0,0 +1,49 @@ +/* + * 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 . + */ +package fr.greencodeinitiative.java.checks; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +public class UseEveryColumnQueriedNonCompliant1 { + + private static final String DB_URL = "jdbc:mysql://localhost/TEST"; + private static final String USER = "guest"; + private static final String PASS = "guest123"; + private static final String QUERY = "SELECT id, first, last, age FROM Registration"; // Noncompliant {{Avoid querying SQL columns that are not used}} + + public void callJdbc() { + + try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(QUERY);) { + while (rs.next()) { + // Display values + System.out.print("ID: " + rs.getInt("id")); + System.out.print(", First: " + rs.getString("first")); + System.out.println(", Last: " + rs.getString("last")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + } +} diff --git a/src/test/files/UseEveryColumnQueriedNonCompliant2.java b/src/test/files/UseEveryColumnQueriedNonCompliant2.java new file mode 100644 index 0000000..8da60f0 --- /dev/null +++ b/src/test/files/UseEveryColumnQueriedNonCompliant2.java @@ -0,0 +1,50 @@ +/* + * 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 . + */ +package fr.greencodeinitiative.java.checks; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +public class UseEveryColumnQueriedNonCompliant2 { + + private static final String DB_URL = "jdbc:mysql://localhost/TEST"; + private static final String USER = "guest"; + private static final String PASS = "guest123"; + + public void callJdbc() { + + try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT id, first, last, age FROM Registration");) { // Noncompliant {{Avoid querying SQL columns that are not used}} + while (rs.next()) { + // Display values + System.out.print("ID: " + rs.getInt("id")); + System.out.print(", First: " + rs.getString("first")); + System.out.println(", Last: " + rs.getString("last")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + } + + +} \ No newline at end of file diff --git a/src/test/files/UseEveryColumnQueriedNonCompliant3.java b/src/test/files/UseEveryColumnQueriedNonCompliant3.java new file mode 100644 index 0000000..775c638 --- /dev/null +++ b/src/test/files/UseEveryColumnQueriedNonCompliant3.java @@ -0,0 +1,49 @@ +/* + * 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 . + */ +package fr.greencodeinitiative.java.checks; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +public class UseEveryColumnQueriedNonCompliant3 { + + private static final String DB_URL = "jdbc:mysql://localhost/TEST"; + private static final String USER = "guest"; + private static final String PASS = "guest123"; + private static final String QUERY = "SELECT id, first, last, age FROM Registration"; // Noncompliant {{Avoid querying SQL columns that are not used}} + + public void callJdbc() { + + try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(QUERY);) { + while (rs.next()) { + // Display values + System.out.print("ID: " + rs.getInt(1)); + System.out.print(", First: " + rs.getString(2)); + System.out.println(", Last: " + rs.getString(3)); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + } +} diff --git a/src/test/files/UseEveryColumnQueriedNonCompliant4.java b/src/test/files/UseEveryColumnQueriedNonCompliant4.java new file mode 100644 index 0000000..86ac6e4 --- /dev/null +++ b/src/test/files/UseEveryColumnQueriedNonCompliant4.java @@ -0,0 +1,49 @@ +/* + * 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 . + */ +package fr.greencodeinitiative.java.checks; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +public class UseEveryColumnQueriedNonCompliant4 { + + private static final String DB_URL = "jdbc:mysql://localhost/TEST"; + private static final String USER = "guest"; + private static final String PASS = "guest123"; + private static final String QUERY = "SELECT id, first, last, age FROM Registration"; // Noncompliant {{Avoid querying SQL columns that are not used}} + + public void callJdbc() { + + try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(QUERY);) { + while (rs.next()) { + // Display values + System.out.print("ID: " + rs.getInt("id")); + System.out.print(", First: " + rs.getString(2)); + System.out.println(", Last: " + rs.getString("last")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + } +} diff --git a/src/test/files/UseEveryColumnQueriedNonCompliant5.java b/src/test/files/UseEveryColumnQueriedNonCompliant5.java new file mode 100644 index 0000000..5c922e5 --- /dev/null +++ b/src/test/files/UseEveryColumnQueriedNonCompliant5.java @@ -0,0 +1,64 @@ +package fr.greencodeinitiative.java.checks; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +/* + * 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 . + */ +public class UseEveryColumnQueriedNonCompliant5 { + + private static final String DB_URL = "jdbc:mysql://localhost/TEST"; + private static final String USER = "guest"; + private static final String PASS = "guest123"; + private static final String QUERY = "SELECT id, first, last, age FROM Registration"; // Noncompliant {{Avoid querying SQL columns that are not used}} + private static final String QUERY2 = "SELECT id, first, last, age FROM Registration2"; + + + public void callJdbc() { + + try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); + Statement stmt = conn.createStatement(); + ) { + + ResultSet rs = stmt.executeQuery(QUERY); + while (rs.next()) { + // Display values + System.out.print("ID: " + rs.getInt("id")); + System.out.print(", First: " + rs.getString("first")); + System.out.println(", Last: " + rs.getString("last")); + } + rs = stmt.executeQuery(QUERY2); + + + while (rs.next()) { + // Display values + System.out.print("Age: " + rs.getInt("age")); + System.out.print("ID: " + rs.getInt("id")); + System.out.print(", First: " + rs.getString("first")); + System.out.println(", Last: " + rs.getString("last")); + } + + + } catch (SQLException e) { + e.printStackTrace(); + } + + } +} diff --git a/src/test/files/UseEveryColumnQueriedNonCompliant6.java b/src/test/files/UseEveryColumnQueriedNonCompliant6.java new file mode 100644 index 0000000..86d6a86 --- /dev/null +++ b/src/test/files/UseEveryColumnQueriedNonCompliant6.java @@ -0,0 +1,52 @@ +/* + * 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 . + */ +package fr.greencodeinitiative.java.checks; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +public class UseEveryColumnQueriedNonCompliant6 { + + private static final String DB_URL = "jdbc:mysql://localhost/TEST"; + private static final String USER = "guest"; + private static final String PASS = "guest123"; + + public void callJdbc() { + + try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT id, first, last, age FROM Registration");) { // Noncompliant {{Avoid querying SQL columns that are not used}} + extractGet(rs); + } catch (SQLException e) { + e.printStackTrace(); + } + + } + + private void extractGet(ResultSet rs) throws SQLException { + while (rs.next()) { + // Display values + System.out.print("ID: " + rs.getInt("id")); + System.out.print(", First: " + rs.getString("first")); + System.out.println(", Last: " + rs.getString("last")); + } + } +} From 9419960f80feb76a3ca193fe01a775ba8e96da0f Mon Sep 17 00:00:00 2001 From: Maxime Daniel Date: Thu, 30 May 2024 10:21:59 +0200 Subject: [PATCH 02/32] feat: :construction: EC1024 skeleton --- .../java/checks/UseEveryColumnQueried.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java diff --git a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java new file mode 100644 index 0000000..73f7dd9 --- /dev/null +++ b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java @@ -0,0 +1,38 @@ +/* + * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs + * Copyright © 2024 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 . + */ +package fr.greencodeinitiative.java.checks; + +import java.util.List; + +import org.sonar.check.Rule; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.tree.Tree.Kind; + +@Rule(key = "1044") +public class UseEveryColumnQueried extends IssuableSubscriptionVisitor { + + protected static final String MESSAGERULE = "Avoid SQL request in loop"; + private static final String JAVA_SQL_STATEMENT = "java.sql.Statement"; + + @Override + public List nodesToVisit() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'nodesToVisit'"); + } + +} From f7905699b2a390e28d026041626698a2670caa14 Mon Sep 17 00:00:00 2001 From: Denielig Date: Thu, 30 May 2024 10:33:51 +0200 Subject: [PATCH 03/32] feat: add test class --- .../checks/UserEveryColumnQueriedTest.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/test/java/fr/greencodeinitiative/java/checks/UserEveryColumnQueriedTest.java diff --git a/src/test/java/fr/greencodeinitiative/java/checks/UserEveryColumnQueriedTest.java b/src/test/java/fr/greencodeinitiative/java/checks/UserEveryColumnQueriedTest.java new file mode 100644 index 0000000..035194d --- /dev/null +++ b/src/test/java/fr/greencodeinitiative/java/checks/UserEveryColumnQueriedTest.java @@ -0,0 +1,52 @@ +/* + * 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 . + */ +package fr.greencodeinitiative.java.checks; + +import org.junit.jupiter.api.Test; +import org.sonar.java.checks.verifier.CheckVerifier; + +class UserEveryColumnQueriedTest { + + @Test + void testHasIssues() { + CheckVerifier.newVerifier() + .onFiles("src/test/files/UseEveryColumnQueriedNonCompliant1.java", + "src/test/files/UseEveryColumnQueriedNonCompliant2.java", + "src/test/files/UseEveryColumnQueriedNonCompliant3.java", + "src/test/files/UseEveryColumnQueriedNonCompliant4.java", + "src/test/files/UseEveryColumnQueriedNonCompliant5.java", + "src/test/files/UseEveryColumnQueriedNonCompliant6.java") + .withCheck(new UseEveryColumnQueried()) + .verifyIssues(); + } + + @Test + void testHasNoIssues() { + CheckVerifier.newVerifier() + .onFiles( + "src/test/files/UseEveryColumnQueriedCompliant1.java", + "src/test/files/UseEveryColumnQueriedCompliant2.java", + "src/test/files/UseEveryColumnQueriedCompliant3.java", + "src/test/files/UseEveryColumnQueriedCompliant4.java", + "src/test/files/UseEveryColumnQueriedCompliant5.java", + "src/test/files/UseEveryColumnQueriedCompliant6.java" + ) + .withCheck(new UseEveryColumnQueried()) + .verifyNoIssues(); + } +} From 2facdf253f689c780366ea2a60b031f3a757ee47 Mon Sep 17 00:00:00 2001 From: Denielig Date: Thu, 30 May 2024 11:46:01 +0200 Subject: [PATCH 04/32] Fix: fix class and file name --- ...eryColumnQueriedTest.java => UseEveryColumnQueriedTest.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/test/java/fr/greencodeinitiative/java/checks/{UserEveryColumnQueriedTest.java => UseEveryColumnQueriedTest.java} (98%) diff --git a/src/test/java/fr/greencodeinitiative/java/checks/UserEveryColumnQueriedTest.java b/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java similarity index 98% rename from src/test/java/fr/greencodeinitiative/java/checks/UserEveryColumnQueriedTest.java rename to src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java index 035194d..6c4bc8d 100644 --- a/src/test/java/fr/greencodeinitiative/java/checks/UserEveryColumnQueriedTest.java +++ b/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java @@ -20,7 +20,7 @@ import org.junit.jupiter.api.Test; import org.sonar.java.checks.verifier.CheckVerifier; -class UserEveryColumnQueriedTest { +class UseEveryColumnQueriedTest { @Test void testHasIssues() { From a9160183263c808c6b94850ff2c2b18be6ff9c24 Mon Sep 17 00:00:00 2001 From: Maxime Daniel Date: Thu, 30 May 2024 12:11:31 +0200 Subject: [PATCH 05/32] feat: :construction: WIP : create rule EC10044 --- .../java/checks/UseEveryColumnQueried.java | 133 +++++++++++++++++- 1 file changed, 128 insertions(+), 5 deletions(-) diff --git a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java index 73f7dd9..4a9914d 100644 --- a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java +++ b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java @@ -1,6 +1,6 @@ /* * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs - * Copyright © 2024 Green Code Initiative (https://www.ecocode.io) + * 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 @@ -17,22 +17,145 @@ */ package fr.greencodeinitiative.java.checks; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.sonar.check.Rule; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.semantic.MethodMatchers; +import org.sonar.plugins.java.api.semantic.Symbol.VariableSymbol; +import org.sonar.plugins.java.api.tree.Arguments; +import org.sonar.plugins.java.api.tree.AssignmentExpressionTree; +import org.sonar.plugins.java.api.tree.ExpressionTree; +import org.sonar.plugins.java.api.tree.IdentifierTree; +import org.sonar.plugins.java.api.tree.LiteralTree; +import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree; +import org.sonar.plugins.java.api.tree.MethodInvocationTree; +import org.sonar.plugins.java.api.tree.Tree; +import org.sonar.plugins.java.api.tree.VariableTree; import org.sonar.plugins.java.api.tree.Tree.Kind; @Rule(key = "1044") public class UseEveryColumnQueried extends IssuableSubscriptionVisitor { - protected static final String MESSAGERULE = "Avoid SQL request in loop"; + protected static final String MESSAGERULE = "Do not request columns that are not used in the query"; private static final String JAVA_SQL_STATEMENT = "java.sql.Statement"; - + private static final String JAVA_SQL_RESULTSET = "java.sql.ResultSet"; + private static final MethodMatchers SQL_STATEMENT_DECLARE_SQL = MethodMatchers + .create() + .ofSubTypes(JAVA_SQL_STATEMENT) + //TODO : also take into account addBatch and executeBatch + .names("executeQuery", "execute", "executeUpdate", "executeLargeUpdate") + .withAnyParameters() + .build(); + private static MethodMatchers SQL_STATEMENT_RETRIEVE_RESULTSET = MethodMatchers + .create() + .ofSubTypes(JAVA_SQL_STATEMENT) + .names("executeQuery", "getResultSet") + // TODO : take into account variable instead of just a string as parameters + .addParametersMatcher("java.lang.String") + .build(); + private static final MethodMatchers SQL_RESULTSET_GET = MethodMatchers + .create() + .ofSubTypes(JAVA_SQL_RESULTSET) + .names("getInt", "getString", "getLong", "getDouble", "getFloat", "getBoolean", "getByte", "getShort", "getBigDecimal", + "getTimestamp", "getDate", "getTime", "getObject", "getArray", "getBlob", "getClob", "getRef", "getRowId", + "getNClob", "getSQLXML", "getURL", "getNString", "getNCharacterStream", "getCharacterStream", "getAsciiStream", "getBinaryStream") + .addParametersMatcher("java.lang.String") + .build(); + private static final Pattern SELECTED_COLUMNS_PATTERN = Pattern.compile("SELECT\\s+(.*)\\s+FROM\\s+.*"); + @Override public List nodesToVisit() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'nodesToVisit'"); + return Arrays.asList(Kind.METHOD_INVOCATION); + } + + @Override + public void visitNode(Tree tree) { + + MethodInvocationTree methodInvocationTree = (MethodInvocationTree) tree; + if(!SQL_STATEMENT_DECLARE_SQL.matches(methodInvocationTree)) { + return; + } + VariableSymbol statement = (VariableSymbol) methodInvocationTree.methodSymbol().owner(); + if(statement == null) { + return; + } + + // STEP 1 : retrieve the selected columns in the query + + Arguments arguments = methodInvocationTree.arguments(); + if(arguments.isEmpty()) { + return; + } + ExpressionTree argument = arguments.get(0); + if(!argument.is(Tree.Kind.STRING_LITERAL)){ + return; + } + String query = ((LiteralTree) argument).value(); + List selectedColumns = extractSelectedSQLColumns(query); + + // STEP 2 : retrieve the resultSet object + + List usages = statement.usages(); + VariableSymbol resultSet = null; + for(IdentifierTree usage : usages) { + Tree parent = usage.parent(); + if(!parent.is(Tree.Kind.MEMBER_SELECT)){ + continue; + } + MemberSelectExpressionTree memberSelect = (MemberSelectExpressionTree) parent; + IdentifierTree identifier = memberSelect.identifier(); + if(!identifier.is(Tree.Kind.METHOD_INVOCATION)){ + continue; + } + MethodInvocationTree methodInvocation = (MethodInvocationTree) identifier; + if(!SQL_STATEMENT_RETRIEVE_RESULTSET.matches(methodInvocation)) { + continue; + } + + while(!parent.is(Tree.Kind.ASSIGNMENT)){ + parent = parent.parent(); + if(parent == null){ + continue; + } + } + ExpressionTree variable = ((AssignmentExpressionTree)parent).variable(); + if(!variable.is(Tree.Kind.VARIABLE)){ + continue; + } + resultSet = ((VariableSymbol)((VariableTree)variable).symbol()); + break; + } + + // STEP 3 : retrieve the columns used from the resultSet object + + if(resultSet == null) { + return; + } + + List usedColumns = new ArrayList<>(); + List resultSetUsages = resultSet.usages(); + for(IdentifierTree usage : resultSetUsages) { + } + + // STEP 4 : compare selected and used columns, report issues + } + + List extractSelectedSQLColumns(String query){ + List columns = new ArrayList<>(); + Matcher matcher = SELECTED_COLUMNS_PATTERN.matcher(query); + if (matcher.matches()) { + String columnString = matcher.group(1); + columns = Arrays.asList(columnString.split(",")); + columns.replaceAll(String::toUpperCase); + columns.replaceAll(column -> column.replaceAll("\\s+", " ")); + columns.replaceAll(column -> column.contains(" AS ") ? column.split(" AS ")[1].trim() : column.trim()); + } + return columns; } } From 5a8eb15f3d4db5b957d22e8a02a5f164f69373ce Mon Sep 17 00:00:00 2001 From: Denielig Date: Thu, 30 May 2024 12:51:46 +0200 Subject: [PATCH 06/32] fix: improve test --- .../checks/UseEveryColumnQueriedTest.java | 102 +++++++++++++++--- 1 file changed, 86 insertions(+), 16 deletions(-) diff --git a/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java b/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java index 6c4bc8d..b73ffc0 100644 --- a/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java +++ b/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java @@ -23,29 +23,99 @@ class UseEveryColumnQueriedTest { @Test - void testHasIssues() { + void testHasIssues1() { CheckVerifier.newVerifier() - .onFiles("src/test/files/UseEveryColumnQueriedNonCompliant1.java", - "src/test/files/UseEveryColumnQueriedNonCompliant2.java", - "src/test/files/UseEveryColumnQueriedNonCompliant3.java", - "src/test/files/UseEveryColumnQueriedNonCompliant4.java", - "src/test/files/UseEveryColumnQueriedNonCompliant5.java", - "src/test/files/UseEveryColumnQueriedNonCompliant6.java") + .onFile("src/test/files/UseEveryColumnQueriedNonCompliant1.java") .withCheck(new UseEveryColumnQueried()) .verifyIssues(); } @Test - void testHasNoIssues() { + void testHasIssues2() { CheckVerifier.newVerifier() - .onFiles( - "src/test/files/UseEveryColumnQueriedCompliant1.java", - "src/test/files/UseEveryColumnQueriedCompliant2.java", - "src/test/files/UseEveryColumnQueriedCompliant3.java", - "src/test/files/UseEveryColumnQueriedCompliant4.java", - "src/test/files/UseEveryColumnQueriedCompliant5.java", - "src/test/files/UseEveryColumnQueriedCompliant6.java" - ) + .onFile("src/test/files/UseEveryColumnQueriedNonCompliant2.java") + .withCheck(new UseEveryColumnQueried()) + .verifyIssues(); + } + + @Test + void testHasIssues3() { + CheckVerifier.newVerifier() + .onFile("src/test/files/UseEveryColumnQueriedNonCompliant3.java") + .withCheck(new UseEveryColumnQueried()) + .verifyIssues(); + } + + @Test + void testHasIssues4() { + CheckVerifier.newVerifier() + .onFile("src/test/files/UseEveryColumnQueriedNonCompliant4.java") + .withCheck(new UseEveryColumnQueried()) + .verifyIssues(); + } + + @Test + void testHasIssues5() { + CheckVerifier.newVerifier() + .onFile("src/test/files/UseEveryColumnQueriedNonCompliant5.java") + .withCheck(new UseEveryColumnQueried()) + .verifyIssues(); + } + + @Test + void testHasIssues6() { + CheckVerifier.newVerifier() + .onFile("src/test/files/UseEveryColumnQueriedNonCompliant6.java") + .withCheck(new UseEveryColumnQueried()) + .verifyIssues(); + } + + + + @Test + void testHasNoIssues1() { + CheckVerifier.newVerifier() + .onFile("src/test/files/UseEveryColumnQueriedCompliant1.java") + .withCheck(new UseEveryColumnQueried()) + .verifyNoIssues(); + } + + @Test + void testHasNoIssues2() { + CheckVerifier.newVerifier() + .onFile("src/test/files/UseEveryColumnQueriedCompliant2.java") + .withCheck(new UseEveryColumnQueried()) + .verifyNoIssues(); + } + + @Test + void testHasNoIssues3() { + CheckVerifier.newVerifier() + .onFile("src/test/files/UseEveryColumnQueriedCompliant3.java") + .withCheck(new UseEveryColumnQueried()) + .verifyNoIssues(); + } + + @Test + void testHasNoIssues4() { + CheckVerifier.newVerifier() + .onFile("src/test/files/UseEveryColumnQueriedCompliant4.java") + .withCheck(new UseEveryColumnQueried()) + .verifyNoIssues(); + } + + @Test + void testHasNoIssues5() { + CheckVerifier.newVerifier() + .onFile("src/test/files/UseEveryColumnQueriedCompliant5.java") + .withCheck(new UseEveryColumnQueried()) + .verifyNoIssues(); + } + + @Test + void testHasNoIssues6() { + CheckVerifier.newVerifier() + .onFile("src/test/files/UseEveryColumnQueriedCompliant6.java") .withCheck(new UseEveryColumnQueried()) .verifyNoIssues(); } From de7897753539925ed1cddddf95a217d4575cade1 Mon Sep 17 00:00:00 2001 From: Maxime Daniel Date: Thu, 30 May 2024 12:56:40 +0200 Subject: [PATCH 07/32] feat: :construction: WIP finish rule creation for EC1044 rule --- .../java/checks/UseEveryColumnQueried.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java index 4a9914d..2292359 100644 --- a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java +++ b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java @@ -26,6 +26,8 @@ import org.sonar.check.Rule; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; import org.sonar.plugins.java.api.semantic.MethodMatchers; +import org.sonar.plugins.java.api.semantic.Symbol; +import org.sonar.plugins.java.api.semantic.Symbol.MethodSymbol; import org.sonar.plugins.java.api.semantic.Symbol.VariableSymbol; import org.sonar.plugins.java.api.tree.Arguments; import org.sonar.plugins.java.api.tree.AssignmentExpressionTree; @@ -140,9 +142,30 @@ public void visitNode(Tree tree) { List usedColumns = new ArrayList<>(); List resultSetUsages = resultSet.usages(); for(IdentifierTree usage : resultSetUsages) { + Tree parent = usage.parent(); + if(!parent.is(Tree.Kind.MEMBER_SELECT)){ + continue; + } + MemberSelectExpressionTree memberSelect = (MemberSelectExpressionTree) parent; + IdentifierTree identifier = memberSelect.identifier(); + if(!identifier.is(Tree.Kind.METHOD_INVOCATION)){ + continue; + } + MethodInvocationTree methodInvocation = (MethodInvocationTree) identifier; + if(!SQL_RESULTSET_GET.matches(methodInvocation)) { + continue; + } + String column = methodInvocation.arguments().get(0).toString(); + usedColumns.add(column); } // STEP 4 : compare selected and used columns, report issues + + selectedColumns.removeAll(usedColumns); + if(!selectedColumns.isEmpty()) { + reportIssue(methodInvocationTree, MESSAGERULE); + } + } List extractSelectedSQLColumns(String query){ From c6eaa43e993e51890888fbbe780f7fc3f6559229 Mon Sep 17 00:00:00 2001 From: Maxime Daniel Date: Thu, 30 May 2024 14:25:52 +0200 Subject: [PATCH 08/32] feat: :construction: WIP : create rule 1044 --- .../java/checks/UseEveryColumnQueried.java | 36 +++++++++++-------- .../checks/UseEveryColumnQueriedTest.java | 15 ++++++++ 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java index 2292359..5ba15fd 100644 --- a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java +++ b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java @@ -23,12 +23,14 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.eclipse.jdt.internal.core.Member; import org.sonar.check.Rule; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; import org.sonar.plugins.java.api.semantic.MethodMatchers; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.Symbol.MethodSymbol; import org.sonar.plugins.java.api.semantic.Symbol.VariableSymbol; +import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.Arguments; import org.sonar.plugins.java.api.tree.AssignmentExpressionTree; import org.sonar.plugins.java.api.tree.ExpressionTree; @@ -82,7 +84,18 @@ public void visitNode(Tree tree) { if(!SQL_STATEMENT_DECLARE_SQL.matches(methodInvocationTree)) { return; } - VariableSymbol statement = (VariableSymbol) methodInvocationTree.methodSymbol().owner(); + ExpressionTree et = methodInvocationTree.methodSelect(); + if(!et.is(Tree.Kind.MEMBER_SELECT)) { + return; + } + MemberSelectExpressionTree mset = (MemberSelectExpressionTree) et; + ExpressionTree expression = mset.expression(); + if(!expression.is(Tree.Kind.IDENTIFIER)) { + return; + } + IdentifierTree id = (IdentifierTree) expression; + Symbol statement = id.symbol(); + //Symbol statement = methodInvocationTree.methodSymbol().owner(); if(statement == null) { return; } @@ -103,7 +116,7 @@ public void visitNode(Tree tree) { // STEP 2 : retrieve the resultSet object List usages = statement.usages(); - VariableSymbol resultSet = null; + Symbol resultSet = null; for(IdentifierTree usage : usages) { Tree parent = usage.parent(); if(!parent.is(Tree.Kind.MEMBER_SELECT)){ @@ -119,17 +132,8 @@ public void visitNode(Tree tree) { continue; } - while(!parent.is(Tree.Kind.ASSIGNMENT)){ - parent = parent.parent(); - if(parent == null){ - continue; - } - } - ExpressionTree variable = ((AssignmentExpressionTree)parent).variable(); - if(!variable.is(Tree.Kind.VARIABLE)){ - continue; - } - resultSet = ((VariableSymbol)((VariableTree)variable).symbol()); + VariableTree resultSetVariable = (VariableTree) methodInvocationTree.parent(); + resultSet = resultSetVariable.symbol(); break; } @@ -168,13 +172,15 @@ public void visitNode(Tree tree) { } - List extractSelectedSQLColumns(String query){ + static List extractSelectedSQLColumns(String query){ + query = query.toUpperCase(); + query = query.replaceAll("^['\"]", ""); + query = query.replaceAll("['\"]$", ""); List columns = new ArrayList<>(); Matcher matcher = SELECTED_COLUMNS_PATTERN.matcher(query); if (matcher.matches()) { String columnString = matcher.group(1); columns = Arrays.asList(columnString.split(",")); - columns.replaceAll(String::toUpperCase); columns.replaceAll(column -> column.replaceAll("\\s+", " ")); columns.replaceAll(column -> column.contains(" AS ") ? column.split(" AS ")[1].trim() : column.trim()); } diff --git a/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java b/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java index b73ffc0..78c8d58 100644 --- a/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java +++ b/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java @@ -17,11 +17,26 @@ */ package fr.greencodeinitiative.java.checks; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + import org.junit.jupiter.api.Test; import org.sonar.java.checks.verifier.CheckVerifier; class UseEveryColumnQueriedTest { + @Test + void testExtractSelectedSQLColumns(){ + String query = "\"SELECT id AS registration_id,\tfirst, last as Final, AGE FROM Registration\""; + List columns = UseEveryColumnQueried.extractSelectedSQLColumns(query); + assertEquals(4, columns.size()); + assertEquals("REGISTRATION_ID", columns.get(0)); + assertEquals("FIRST", columns.get(1)); + assertEquals("FINAL", columns.get(2)); + assertEquals("AGE", columns.get(3)); + } + @Test void testHasIssues1() { CheckVerifier.newVerifier() From 75196b8601fefbafca514c5ea03bc79aa5691afc Mon Sep 17 00:00:00 2001 From: Maxime Daniel Date: Thu, 30 May 2024 15:17:48 +0200 Subject: [PATCH 09/32] feat: :sparkles: created rule 1044 --- .../java/checks/UseEveryColumnQueried.java | 68 ++++++++++--------- .../checks/UseEveryColumnQueriedTest.java | 7 ++ 2 files changed, 43 insertions(+), 32 deletions(-) diff --git a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java index 5ba15fd..483dc15 100644 --- a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java +++ b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.eclipse.jdt.internal.core.Member; import org.sonar.check.Rule; @@ -45,7 +46,7 @@ @Rule(key = "1044") public class UseEveryColumnQueried extends IssuableSubscriptionVisitor { - protected static final String MESSAGERULE = "Do not request columns that are not used in the query"; + protected static final String MESSAGERULE = "Avoid querying SQL columns that are not used"; private static final String JAVA_SQL_STATEMENT = "java.sql.Statement"; private static final String JAVA_SQL_RESULTSET = "java.sql.ResultSet"; private static final MethodMatchers SQL_STATEMENT_DECLARE_SQL = MethodMatchers @@ -53,14 +54,14 @@ public class UseEveryColumnQueried extends IssuableSubscriptionVisitor { .ofSubTypes(JAVA_SQL_STATEMENT) //TODO : also take into account addBatch and executeBatch .names("executeQuery", "execute", "executeUpdate", "executeLargeUpdate") - .withAnyParameters() + // TODO : take into account variable instead of just a string as parameters + .addParametersMatcher("java.lang.String") .build(); private static MethodMatchers SQL_STATEMENT_RETRIEVE_RESULTSET = MethodMatchers .create() .ofSubTypes(JAVA_SQL_STATEMENT) .names("executeQuery", "getResultSet") - // TODO : take into account variable instead of just a string as parameters - .addParametersMatcher("java.lang.String") + .withAnyParameters() .build(); private static final MethodMatchers SQL_RESULTSET_GET = MethodMatchers .create() @@ -95,7 +96,6 @@ public void visitNode(Tree tree) { } IdentifierTree id = (IdentifierTree) expression; Symbol statement = id.symbol(); - //Symbol statement = methodInvocationTree.methodSymbol().owner(); if(statement == null) { return; } @@ -118,22 +118,11 @@ public void visitNode(Tree tree) { List usages = statement.usages(); Symbol resultSet = null; for(IdentifierTree usage : usages) { - Tree parent = usage.parent(); - if(!parent.is(Tree.Kind.MEMBER_SELECT)){ + Tree methodInvocation = getMethodInvocationFromTree(usage); + if(methodInvocation == null){ continue; } - MemberSelectExpressionTree memberSelect = (MemberSelectExpressionTree) parent; - IdentifierTree identifier = memberSelect.identifier(); - if(!identifier.is(Tree.Kind.METHOD_INVOCATION)){ - continue; - } - MethodInvocationTree methodInvocation = (MethodInvocationTree) identifier; - if(!SQL_STATEMENT_RETRIEVE_RESULTSET.matches(methodInvocation)) { - continue; - } - - VariableTree resultSetVariable = (VariableTree) methodInvocationTree.parent(); - resultSet = resultSetVariable.symbol(); + resultSet = ((VariableTree) methodInvocationTree.parent()).symbol(); break; } @@ -146,32 +135,47 @@ public void visitNode(Tree tree) { List usedColumns = new ArrayList<>(); List resultSetUsages = resultSet.usages(); for(IdentifierTree usage : resultSetUsages) { - Tree parent = usage.parent(); - if(!parent.is(Tree.Kind.MEMBER_SELECT)){ - continue; - } - MemberSelectExpressionTree memberSelect = (MemberSelectExpressionTree) parent; - IdentifierTree identifier = memberSelect.identifier(); - if(!identifier.is(Tree.Kind.METHOD_INVOCATION)){ + MethodInvocationTree methodInvocation = getMethodInvocationFromTree(usage); + if(!SQL_RESULTSET_GET.matches(methodInvocation)) { continue; } - MethodInvocationTree methodInvocation = (MethodInvocationTree) identifier; - if(!SQL_RESULTSET_GET.matches(methodInvocation)) { + ExpressionTree columnET = methodInvocation.arguments().get(0); + if(!columnET.is(Tree.Kind.STRING_LITERAL)) { continue; } - String column = methodInvocation.arguments().get(0).toString(); + String column = ((LiteralTree) columnET).value(); + + column = column.toUpperCase(); + column = column.replaceAll("^['\"]", ""); + column = column.replaceAll("['\"]$", ""); usedColumns.add(column); } // STEP 4 : compare selected and used columns, report issues - - selectedColumns.removeAll(usedColumns); - if(!selectedColumns.isEmpty()) { + List differences = selectedColumns.stream() + .filter(element -> !usedColumns.contains(element)) + .collect(Collectors.toList()); + if(!differences.isEmpty()) { reportIssue(methodInvocationTree, MESSAGERULE); } } + private static MethodInvocationTree getMethodInvocationFromTree(Tree tree) { + Tree parent = tree.parent(); + if(!parent.is(Tree.Kind.MEMBER_SELECT)){ + return null; + } + while(!parent.is(Tree.Kind.METHOD_INVOCATION) && parent != null){ + parent = parent.parent(); + } + if(parent == null){ + return null; + } + + return (MethodInvocationTree) parent; + } + static List extractSelectedSQLColumns(String query){ query = query.toUpperCase(); query = query.replaceAll("^['\"]", ""); diff --git a/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java b/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java index 78c8d58..8ec3fa7 100644 --- a/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java +++ b/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java @@ -21,6 +21,7 @@ import java.util.List; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.sonar.java.checks.verifier.CheckVerifier; @@ -38,6 +39,7 @@ void testExtractSelectedSQLColumns(){ } @Test + @Disabled void testHasIssues1() { CheckVerifier.newVerifier() .onFile("src/test/files/UseEveryColumnQueriedNonCompliant1.java") @@ -54,6 +56,7 @@ void testHasIssues2() { } @Test + @Disabled void testHasIssues3() { CheckVerifier.newVerifier() .onFile("src/test/files/UseEveryColumnQueriedNonCompliant3.java") @@ -62,6 +65,7 @@ void testHasIssues3() { } @Test + @Disabled void testHasIssues4() { CheckVerifier.newVerifier() .onFile("src/test/files/UseEveryColumnQueriedNonCompliant4.java") @@ -70,6 +74,7 @@ void testHasIssues4() { } @Test + @Disabled void testHasIssues5() { CheckVerifier.newVerifier() .onFile("src/test/files/UseEveryColumnQueriedNonCompliant5.java") @@ -78,6 +83,7 @@ void testHasIssues5() { } @Test + @Disabled void testHasIssues6() { CheckVerifier.newVerifier() .onFile("src/test/files/UseEveryColumnQueriedNonCompliant6.java") @@ -128,6 +134,7 @@ void testHasNoIssues5() { } @Test + @Disabled void testHasNoIssues6() { CheckVerifier.newVerifier() .onFile("src/test/files/UseEveryColumnQueriedCompliant6.java") From ea027b0b28513e170dac7f381ffe4513639b5db6 Mon Sep 17 00:00:00 2001 From: jycr Date: Thu, 30 May 2024 13:15:14 +0200 Subject: [PATCH 10/32] Add test to ensure all Rules are registered --- pom.xml | 6 ++++++ .../java/JavaCheckRegistrar.java | 2 +- .../java/JavaCheckRegistrarTest.java | 14 +++++++++++--- .../java/JavaRulesDefinitionTest.java | 7 ++----- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index fa0c7e9..c6df47a 100644 --- a/pom.xml +++ b/pom.xml @@ -136,6 +136,12 @@ test + + org.reflections + reflections + 0.10.2 + test + diff --git a/src/main/java/fr/greencodeinitiative/java/JavaCheckRegistrar.java b/src/main/java/fr/greencodeinitiative/java/JavaCheckRegistrar.java index f4ef343..50ada18 100644 --- a/src/main/java/fr/greencodeinitiative/java/JavaCheckRegistrar.java +++ b/src/main/java/fr/greencodeinitiative/java/JavaCheckRegistrar.java @@ -47,7 +47,7 @@ */ @SonarLintSide public class JavaCheckRegistrar implements CheckRegistrar { - private static final List> ANNOTATED_RULE_CLASSES = List.of( + static final List> ANNOTATED_RULE_CLASSES = List.of( ArrayCopyCheck.class, IncrementCheck.class, AvoidUsageOfStaticCollections.class, diff --git a/src/test/java/fr/greencodeinitiative/java/JavaCheckRegistrarTest.java b/src/test/java/fr/greencodeinitiative/java/JavaCheckRegistrarTest.java index 02270ca..09655b6 100644 --- a/src/test/java/fr/greencodeinitiative/java/JavaCheckRegistrarTest.java +++ b/src/test/java/fr/greencodeinitiative/java/JavaCheckRegistrarTest.java @@ -17,7 +17,11 @@ */ package fr.greencodeinitiative.java; +import java.util.Set; + import org.junit.jupiter.api.Test; +import org.reflections.Reflections; +import org.sonar.check.Rule; import org.sonar.plugins.java.api.CheckRegistrar; import static org.assertj.core.api.Assertions.assertThat; @@ -30,10 +34,14 @@ void checkNumberRules() { final JavaCheckRegistrar registrar = new JavaCheckRegistrar(); registrar.register(context); - - assertThat(context.checkClasses()).hasSize(15); + assertThat(context.checkClasses()) + .describedAs("All implemented rules must be registered into " + JavaCheckRegistrar.class) + .containsExactlyInAnyOrder(getDefinedRules().toArray(new Class[0])); assertThat(context.testCheckClasses()).isEmpty(); - } + static Set> getDefinedRules() { + Reflections r = new Reflections(JavaCheckRegistrar.class.getPackageName() + ".checks"); + return r.getTypesAnnotatedWith(Rule.class); + } } diff --git a/src/test/java/fr/greencodeinitiative/java/JavaRulesDefinitionTest.java b/src/test/java/fr/greencodeinitiative/java/JavaRulesDefinitionTest.java index ad1b536..ee6dff5 100644 --- a/src/test/java/fr/greencodeinitiative/java/JavaRulesDefinitionTest.java +++ b/src/test/java/fr/greencodeinitiative/java/JavaRulesDefinitionTest.java @@ -28,6 +28,7 @@ import org.sonar.api.server.rule.RulesDefinition.Rule; import org.sonar.api.utils.Version; +import static fr.greencodeinitiative.java.JavaCheckRegistrar.ANNOTATED_RULE_CLASSES; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -36,8 +37,6 @@ class JavaRulesDefinitionTest { private RulesDefinition.Repository repository; - private int rulesSize; - @BeforeEach void init() { final SonarRuntime sonarRuntime = mock(SonarRuntime.class); @@ -46,7 +45,6 @@ void init() { RulesDefinition.Context context = new RulesDefinition.Context(); rulesDefinition.define(context); repository = context.repository(rulesDefinition.repositoryKey()); - rulesSize = 15; } @Test @@ -55,12 +53,11 @@ void testMetadata() { assertThat(repository.name()).isEqualTo("ecoCode"); assertThat(repository.language()).isEqualTo("java"); assertThat(repository.key()).isEqualTo("ecocode-java"); - assertThat(repository.rules()).hasSize(rulesSize); } @Test void testRegistredRules() { - assertThat(repository.rules()).hasSize(rulesSize); + assertThat(repository.rules()).hasSize(ANNOTATED_RULE_CLASSES.size()); } @Test From e82572d555ed7178370c50654ade3aa7668073e7 Mon Sep 17 00:00:00 2001 From: Maxime Daniel Date: Mon, 10 Jun 2024 16:46:43 +0200 Subject: [PATCH 11/32] fix: :bug: improve false positives detection --- .../java/checks/UseEveryColumnQueried.java | 146 ++++++++++++++---- .../UseEveryColumnQueriedCompliant3.java | 8 +- .../UseEveryColumnQueriedCompliant5.java | 3 +- .../UseEveryColumnQueriedNonCompliant1.java | 4 +- .../UseEveryColumnQueriedNonCompliant3.java | 4 +- .../UseEveryColumnQueriedNonCompliant4.java | 4 +- .../UseEveryColumnQueriedNonCompliant5.java | 5 +- .../checks/UseEveryColumnQueriedTest.java | 7 +- 8 files changed, 130 insertions(+), 51 deletions(-) diff --git a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java index 483dc15..ac12fe4 100644 --- a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java +++ b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java @@ -24,12 +24,10 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -import org.eclipse.jdt.internal.core.Member; import org.sonar.check.Rule; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; import org.sonar.plugins.java.api.semantic.MethodMatchers; import org.sonar.plugins.java.api.semantic.Symbol; -import org.sonar.plugins.java.api.semantic.Symbol.MethodSymbol; import org.sonar.plugins.java.api.semantic.Symbol.VariableSymbol; import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.Arguments; @@ -54,7 +52,6 @@ public class UseEveryColumnQueried extends IssuableSubscriptionVisitor { .ofSubTypes(JAVA_SQL_STATEMENT) //TODO : also take into account addBatch and executeBatch .names("executeQuery", "execute", "executeUpdate", "executeLargeUpdate") - // TODO : take into account variable instead of just a string as parameters .addParametersMatcher("java.lang.String") .build(); private static MethodMatchers SQL_STATEMENT_RETRIEVE_RESULTSET = MethodMatchers @@ -63,7 +60,7 @@ public class UseEveryColumnQueried extends IssuableSubscriptionVisitor { .names("executeQuery", "getResultSet") .withAnyParameters() .build(); - private static final MethodMatchers SQL_RESULTSET_GET = MethodMatchers + private static final MethodMatchers SQL_RESULTSET_GET_COLNAME = MethodMatchers .create() .ofSubTypes(JAVA_SQL_RESULTSET) .names("getInt", "getString", "getLong", "getDouble", "getFloat", "getBoolean", "getByte", "getShort", "getBigDecimal", @@ -71,6 +68,14 @@ public class UseEveryColumnQueried extends IssuableSubscriptionVisitor { "getNClob", "getSQLXML", "getURL", "getNString", "getNCharacterStream", "getCharacterStream", "getAsciiStream", "getBinaryStream") .addParametersMatcher("java.lang.String") .build(); + private static final MethodMatchers SQL_RESULTSET_GET_COLID = MethodMatchers + .create() + .ofSubTypes(JAVA_SQL_RESULTSET) + .names("getInt", "getString", "getLong", "getDouble", "getFloat", "getBoolean", "getByte", "getShort", "getBigDecimal", + "getTimestamp", "getDate", "getTime", "getObject", "getArray", "getBlob", "getClob", "getRef", "getRowId", + "getNClob", "getSQLXML", "getURL", "getNString", "getNCharacterStream", "getCharacterStream", "getAsciiStream", "getBinaryStream") + .addParametersMatcher("int") + .build(); private static final Pattern SELECTED_COLUMNS_PATTERN = Pattern.compile("SELECT\\s+(.*)\\s+FROM\\s+.*"); @Override @@ -107,10 +112,17 @@ public void visitNode(Tree tree) { return; } ExpressionTree argument = arguments.get(0); - if(!argument.is(Tree.Kind.STRING_LITERAL)){ + String query; + if(argument.is(Tree.Kind.STRING_LITERAL)){ + query = ((LiteralTree) argument).value(); + } else if(argument.is(Tree.Kind.IDENTIFIER)){ + query = extractValueFromVariable((IdentifierTree) argument); + } else { + return; + } + if(query == null){ return; } - String query = ((LiteralTree) argument).value(); List selectedColumns = extractSelectedSQLColumns(query); // STEP 2 : retrieve the resultSet object @@ -118,37 +130,79 @@ public void visitNode(Tree tree) { List usages = statement.usages(); Symbol resultSet = null; for(IdentifierTree usage : usages) { - Tree methodInvocation = getMethodInvocationFromTree(usage); - if(methodInvocation == null){ + MethodInvocationTree methodInvocation = getMethodInvocationFromTree(usage); + if(methodInvocation == null ){ continue; } - resultSet = ((VariableTree) methodInvocationTree.parent()).symbol(); - break; + if(SQL_STATEMENT_RETRIEVE_RESULTSET.matches(methodInvocation)){ + Tree parent = methodInvocation.parent(); + if(parent.is(Tree.Kind.VARIABLE)){ + resultSet = ((VariableTree) parent).symbol(); + break; + } + } } - // STEP 3 : retrieve the columns used from the resultSet object - + // STEP 2.1 check if the resultSet is used as a parameter of a method + // if it is, this check this check cannot be applied if(resultSet == null) { return; } + List resultSetUsages = resultSet.usages(); + for(IdentifierTree usage : resultSetUsages) { + Tree parent = usage.parent(); + if(parent.is(Tree.Kind.ARGUMENTS)){ + return; + } + } + + // STEP 2.2 check if the resultSet is reassigned + for(IdentifierTree usage : resultSetUsages) { + Tree parent = usage.parent(); + if(parent.is(Tree.Kind.ASSIGNMENT)){ + AssignmentExpressionTree assignment = (AssignmentExpressionTree) parent; + ExpressionTree expressionTree = assignment.variable(); + if(expressionTree.is(Tree.Kind.IDENTIFIER)){ + if(resultSet.equals(((IdentifierTree) expressionTree).symbol())) { + return; + } + } + } + } + + // STEP 3 : retrieve the columns used from the resultSet object List usedColumns = new ArrayList<>(); - List resultSetUsages = resultSet.usages(); for(IdentifierTree usage : resultSetUsages) { + if(usage.parent().is(Tree.Kind.ASSIGNMENT)){ + break; + } MethodInvocationTree methodInvocation = getMethodInvocationFromTree(usage); - if(!SQL_RESULTSET_GET.matches(methodInvocation)) { + if(methodInvocation == null ){ continue; } - ExpressionTree columnET = methodInvocation.arguments().get(0); - if(!columnET.is(Tree.Kind.STRING_LITERAL)) { - continue; + if(SQL_RESULTSET_GET_COLNAME.matches(methodInvocation)) { + ExpressionTree columnET = methodInvocation.arguments().get(0); + if(!columnET.is(Tree.Kind.STRING_LITERAL)) { + continue; + } + String column = ((LiteralTree) columnET).value(); + + column = column.toUpperCase(); + column = column.replaceAll("^['\"]", ""); + column = column.replaceAll("['\"]$", ""); + usedColumns.add(column); + } else if(SQL_RESULTSET_GET_COLID.matches(methodInvocation)) { + ExpressionTree columnET = methodInvocation.arguments().get(0); + if(!columnET.is(Tree.Kind.INT_LITERAL)) { + continue; + } + int column = Integer.parseInt(((LiteralTree) columnET).value()); + if(column > selectedColumns.size()) { + continue; + } + usedColumns.add(selectedColumns.get(column - 1)); } - String column = ((LiteralTree) columnET).value(); - - column = column.toUpperCase(); - column = column.replaceAll("^['\"]", ""); - column = column.replaceAll("['\"]$", ""); - usedColumns.add(column); } // STEP 4 : compare selected and used columns, report issues @@ -161,21 +215,53 @@ public void visitNode(Tree tree) { } - private static MethodInvocationTree getMethodInvocationFromTree(Tree tree) { - Tree parent = tree.parent(); - if(!parent.is(Tree.Kind.MEMBER_SELECT)){ - return null; - } - while(!parent.is(Tree.Kind.METHOD_INVOCATION) && parent != null){ + private static MethodInvocationTree getMethodInvocationFromTree(IdentifierTree tree) { + Tree parent = tree; + while(parent != null && !parent.is(Tree.Kind.METHOD_INVOCATION) ){ parent = parent.parent(); } if(parent == null){ return null; } - return (MethodInvocationTree) parent; } + private static String extractValueFromVariable(IdentifierTree tree){ + Symbol symbol = tree.symbol(); + if(symbol == null) { + return null; + } + //accept this value if it's a final variable or it's a variable that is not reassigned + if(!symbol.isFinal()){ + List usages = symbol.usages(); + int assignementCount = 0; + for(IdentifierTree usage : usages) { + Tree parent = usage.parent(); + if(parent.is(Tree.Kind.ASSIGNMENT)){ + assignementCount++; + } + } + if(assignementCount > 1){ + return null; + } + } + if(symbol.isVariableSymbol()) { + VariableSymbol variableSymbol = (VariableSymbol) symbol; + Type type = variableSymbol.type(); + if(type.is("java.lang.String")) { + Tree assignement = variableSymbol.declaration(); + if(assignement.is(Tree.Kind.VARIABLE)){ + VariableTree variableTree = (VariableTree) assignement; + ExpressionTree initializer = variableTree.initializer(); + if(initializer.is(Tree.Kind.STRING_LITERAL)){ + return ((LiteralTree) initializer).value(); + } + } + } + } + return null; + } + static List extractSelectedSQLColumns(String query){ query = query.toUpperCase(); query = query.replaceAll("^['\"]", ""); diff --git a/src/test/files/UseEveryColumnQueriedCompliant3.java b/src/test/files/UseEveryColumnQueriedCompliant3.java index a6f0fe7..e2a5082 100644 --- a/src/test/files/UseEveryColumnQueriedCompliant3.java +++ b/src/test/files/UseEveryColumnQueriedCompliant3.java @@ -37,10 +37,10 @@ public void callJdbc() { ResultSet rs = stmt.executeQuery(QUERY);) { while (rs.next()) { // Display values - System.out.print("ID: " + rs.getInt("1")); - System.out.print(", Age: " + rs.getInt("4")); - System.out.print(", First: " + rs.getString("2")); - System.out.println(", Last: " + rs.getString("3")); + System.out.print("ID: " + rs.getInt(1)); + System.out.print(", Age: " + rs.getInt(4)); + System.out.print(", First: " + rs.getString(2)); + System.out.println(", Last: " + rs.getString(3)); } } catch (SQLException e) { e.printStackTrace(); diff --git a/src/test/files/UseEveryColumnQueriedCompliant5.java b/src/test/files/UseEveryColumnQueriedCompliant5.java index 7fedb2a..4f5f68c 100644 --- a/src/test/files/UseEveryColumnQueriedCompliant5.java +++ b/src/test/files/UseEveryColumnQueriedCompliant5.java @@ -29,7 +29,7 @@ public class UseEveryColumnQueriedCompliant5 { private static final String USER = "guest"; private static final String PASS = "guest123"; private static final String QUERY = "SELECT id, first, last, age FROM Registration"; - private static final String QUERY2 = "SELECT id, first, last, age FROM Registration2"; + private static final String QUERY2 = "SELECT id, first, last FROM Registration2"; public void callJdbc() { @@ -51,7 +51,6 @@ public void callJdbc() { while (rs.next()) { // Display values - System.out.print("Age: " + rs.getInt("age")); System.out.print("ID: " + rs.getInt("id")); System.out.print(", First: " + rs.getString("first")); System.out.println(", Last: " + rs.getString("last")); diff --git a/src/test/files/UseEveryColumnQueriedNonCompliant1.java b/src/test/files/UseEveryColumnQueriedNonCompliant1.java index 0daf184..1c40dbb 100644 --- a/src/test/files/UseEveryColumnQueriedNonCompliant1.java +++ b/src/test/files/UseEveryColumnQueriedNonCompliant1.java @@ -28,13 +28,13 @@ public class UseEveryColumnQueriedNonCompliant1 { private static final String DB_URL = "jdbc:mysql://localhost/TEST"; private static final String USER = "guest"; private static final String PASS = "guest123"; - private static final String QUERY = "SELECT id, first, last, age FROM Registration"; // Noncompliant {{Avoid querying SQL columns that are not used}} + private static final String QUERY = "SELECT id, first, last, age FROM Registration"; public void callJdbc() { try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); Statement stmt = conn.createStatement(); - ResultSet rs = stmt.executeQuery(QUERY);) { + ResultSet rs = stmt.executeQuery(QUERY);) { // Noncompliant {{Avoid querying SQL columns that are not used}} while (rs.next()) { // Display values System.out.print("ID: " + rs.getInt("id")); diff --git a/src/test/files/UseEveryColumnQueriedNonCompliant3.java b/src/test/files/UseEveryColumnQueriedNonCompliant3.java index 775c638..50afcc5 100644 --- a/src/test/files/UseEveryColumnQueriedNonCompliant3.java +++ b/src/test/files/UseEveryColumnQueriedNonCompliant3.java @@ -28,13 +28,13 @@ public class UseEveryColumnQueriedNonCompliant3 { private static final String DB_URL = "jdbc:mysql://localhost/TEST"; private static final String USER = "guest"; private static final String PASS = "guest123"; - private static final String QUERY = "SELECT id, first, last, age FROM Registration"; // Noncompliant {{Avoid querying SQL columns that are not used}} + private static final String QUERY = "SELECT id, first, last, age FROM Registration"; public void callJdbc() { try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); Statement stmt = conn.createStatement(); - ResultSet rs = stmt.executeQuery(QUERY);) { + ResultSet rs = stmt.executeQuery(QUERY);) { // Noncompliant {{Avoid querying SQL columns that are not used}} while (rs.next()) { // Display values System.out.print("ID: " + rs.getInt(1)); diff --git a/src/test/files/UseEveryColumnQueriedNonCompliant4.java b/src/test/files/UseEveryColumnQueriedNonCompliant4.java index 86ac6e4..0023523 100644 --- a/src/test/files/UseEveryColumnQueriedNonCompliant4.java +++ b/src/test/files/UseEveryColumnQueriedNonCompliant4.java @@ -28,13 +28,13 @@ public class UseEveryColumnQueriedNonCompliant4 { private static final String DB_URL = "jdbc:mysql://localhost/TEST"; private static final String USER = "guest"; private static final String PASS = "guest123"; - private static final String QUERY = "SELECT id, first, last, age FROM Registration"; // Noncompliant {{Avoid querying SQL columns that are not used}} + private static final String QUERY = "SELECT id, first, last, age FROM Registration"; public void callJdbc() { try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); Statement stmt = conn.createStatement(); - ResultSet rs = stmt.executeQuery(QUERY);) { + ResultSet rs = stmt.executeQuery(QUERY);) { // Noncompliant {{Avoid querying SQL columns that are not used}} while (rs.next()) { // Display values System.out.print("ID: " + rs.getInt("id")); diff --git a/src/test/files/UseEveryColumnQueriedNonCompliant5.java b/src/test/files/UseEveryColumnQueriedNonCompliant5.java index 5c922e5..959bc80 100644 --- a/src/test/files/UseEveryColumnQueriedNonCompliant5.java +++ b/src/test/files/UseEveryColumnQueriedNonCompliant5.java @@ -27,7 +27,7 @@ public class UseEveryColumnQueriedNonCompliant5 { private static final String DB_URL = "jdbc:mysql://localhost/TEST"; private static final String USER = "guest"; private static final String PASS = "guest123"; - private static final String QUERY = "SELECT id, first, last, age FROM Registration"; // Noncompliant {{Avoid querying SQL columns that are not used}} + private static final String QUERY = "SELECT id, first, last, age FROM Registration"; private static final String QUERY2 = "SELECT id, first, last, age FROM Registration2"; @@ -37,7 +37,7 @@ public void callJdbc() { Statement stmt = conn.createStatement(); ) { - ResultSet rs = stmt.executeQuery(QUERY); + ResultSet rs = stmt.executeQuery(QUERY); // Noncompliant {{Avoid querying SQL columns that are not used}} while (rs.next()) { // Display values System.out.print("ID: " + rs.getInt("id")); @@ -46,7 +46,6 @@ public void callJdbc() { } rs = stmt.executeQuery(QUERY2); - while (rs.next()) { // Display values System.out.print("Age: " + rs.getInt("age")); diff --git a/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java b/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java index 8ec3fa7..df1ab3b 100644 --- a/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java +++ b/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java @@ -39,7 +39,6 @@ void testExtractSelectedSQLColumns(){ } @Test - @Disabled void testHasIssues1() { CheckVerifier.newVerifier() .onFile("src/test/files/UseEveryColumnQueriedNonCompliant1.java") @@ -56,7 +55,6 @@ void testHasIssues2() { } @Test - @Disabled void testHasIssues3() { CheckVerifier.newVerifier() .onFile("src/test/files/UseEveryColumnQueriedNonCompliant3.java") @@ -65,7 +63,6 @@ void testHasIssues3() { } @Test - @Disabled void testHasIssues4() { CheckVerifier.newVerifier() .onFile("src/test/files/UseEveryColumnQueriedNonCompliant4.java") @@ -74,7 +71,6 @@ void testHasIssues4() { } @Test - @Disabled void testHasIssues5() { CheckVerifier.newVerifier() .onFile("src/test/files/UseEveryColumnQueriedNonCompliant5.java") @@ -83,7 +79,7 @@ void testHasIssues5() { } @Test - @Disabled + @Disabled // case not handled yet void testHasIssues6() { CheckVerifier.newVerifier() .onFile("src/test/files/UseEveryColumnQueriedNonCompliant6.java") @@ -134,7 +130,6 @@ void testHasNoIssues5() { } @Test - @Disabled void testHasNoIssues6() { CheckVerifier.newVerifier() .onFile("src/test/files/UseEveryColumnQueriedCompliant6.java") From 759b8ad7701c5e49f9bb36ebf61884162d2c41b9 Mon Sep 17 00:00:00 2001 From: Maxime Daniel Date: Tue, 11 Jun 2024 14:40:46 +0200 Subject: [PATCH 12/32] refactor: :recycle: quick refactor --- .../java/checks/UseEveryColumnQueried.java | 245 ++++++++++-------- .../UseEveryColumnQueriedCompliant4.java | 6 +- .../UseEveryColumnQueriedNonCompliant4.java | 3 +- .../checks/UseEveryColumnQueriedTest.java | 3 +- 4 files changed, 138 insertions(+), 119 deletions(-) diff --git a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java index ac12fe4..f45fd80 100644 --- a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java +++ b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java @@ -24,12 +24,13 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; +import javax.annotation.Nullable; + import org.sonar.check.Rule; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; import org.sonar.plugins.java.api.semantic.MethodMatchers; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.Symbol.VariableSymbol; -import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.Arguments; import org.sonar.plugins.java.api.tree.AssignmentExpressionTree; import org.sonar.plugins.java.api.tree.ExpressionTree; @@ -90,131 +91,117 @@ public void visitNode(Tree tree) { if(!SQL_STATEMENT_DECLARE_SQL.matches(methodInvocationTree)) { return; } - ExpressionTree et = methodInvocationTree.methodSelect(); - if(!et.is(Tree.Kind.MEMBER_SELECT)) { + List selectedColumns = getSelectedColumns(methodInvocationTree); + if(selectedColumns.isEmpty()) { return; } - MemberSelectExpressionTree mset = (MemberSelectExpressionTree) et; - ExpressionTree expression = mset.expression(); - if(!expression.is(Tree.Kind.IDENTIFIER)) { + List usedColumns = getUsedColumns(methodInvocationTree, selectedColumns); + if(usedColumns == null) { return; } - IdentifierTree id = (IdentifierTree) expression; - Symbol statement = id.symbol(); - if(statement == null) { - return; + List differences = selectedColumns.stream() + .filter(element -> !usedColumns.contains(element)) + .collect(Collectors.toList()); + if(!differences.isEmpty()) { + reportIssue(methodInvocationTree, MESSAGERULE); } + } - // STEP 1 : retrieve the selected columns in the query - + private static List getSelectedColumns(MethodInvocationTree methodInvocationTree) { Arguments arguments = methodInvocationTree.arguments(); if(arguments.isEmpty()) { - return; + return new ArrayList<>(); } ExpressionTree argument = arguments.get(0); - String query; - if(argument.is(Tree.Kind.STRING_LITERAL)){ - query = ((LiteralTree) argument).value(); - } else if(argument.is(Tree.Kind.IDENTIFIER)){ - query = extractValueFromVariable((IdentifierTree) argument); - } else { - return; - } - if(query == null){ - return; + LiteralTree litteral = extractLiteralFromVariable(argument); + if(litteral == null) { + return new ArrayList<>(); } + String query = litteral.value(); List selectedColumns = extractSelectedSQLColumns(query); + return selectedColumns; + } - // STEP 2 : retrieve the resultSet object + @Nullable + private static List getUsedColumns(MethodInvocationTree methodInvocationTree, List selectedColumns){ + Symbol resultSet = getResultSetNode(methodInvocationTree); - List usages = statement.usages(); - Symbol resultSet = null; - for(IdentifierTree usage : usages) { + if(resultSet == null || isResultSetInvalid(resultSet)) { + return null; + } + + List usedColumns = new ArrayList<>(); + List resultSetUsages = resultSet.usages(); + for(IdentifierTree usage : resultSetUsages) { + if(usage.parent().is(Tree.Kind.ASSIGNMENT)){ + break; + } MethodInvocationTree methodInvocation = getMethodInvocationFromTree(usage); if(methodInvocation == null ){ continue; } - if(SQL_STATEMENT_RETRIEVE_RESULTSET.matches(methodInvocation)){ - Tree parent = methodInvocation.parent(); - if(parent.is(Tree.Kind.VARIABLE)){ - resultSet = ((VariableTree) parent).symbol(); + if(methodInvocation.arguments().isEmpty()){ + continue; + } + ExpressionTree parameter = methodInvocation.arguments().get(0); + LiteralTree columnGot = extractLiteralFromVariable(parameter); + if(columnGot == null){ + continue; + } + String column; + String value = columnGot.value(); + if(SQL_RESULTSET_GET_COLNAME.matches(methodInvocation) && columnGot.is(Tree.Kind.STRING_LITERAL)) { + column = value.toUpperCase(); + column = column.replaceAll("^['\"]", ""); + column = column.replaceAll("['\"]$", ""); + } else if(SQL_RESULTSET_GET_COLID.matches(methodInvocation) && columnGot.is(Tree.Kind.INT_LITERAL)) { + int columnId = Integer.parseInt(value); + if(columnId > selectedColumns.size()) { break; } + column = selectedColumns.get(columnId - 1); + } else { + continue; } + usedColumns.add(column); } + return usedColumns; + } - // STEP 2.1 check if the resultSet is used as a parameter of a method - // if it is, this check this check cannot be applied - if(resultSet == null) { - return; + @Nullable + private static Symbol getResultSetNode(MethodInvocationTree methodInvocationTree) { + ExpressionTree et = methodInvocationTree.methodSelect(); + if(!et.is(Tree.Kind.MEMBER_SELECT)) { + return null; } - List resultSetUsages = resultSet.usages(); - for(IdentifierTree usage : resultSetUsages) { - Tree parent = usage.parent(); - if(parent.is(Tree.Kind.ARGUMENTS)){ - return; - } + MemberSelectExpressionTree mset = (MemberSelectExpressionTree) et; + ExpressionTree expression = mset.expression(); + if(!expression.is(Tree.Kind.IDENTIFIER)) { + return null; } - - // STEP 2.2 check if the resultSet is reassigned - for(IdentifierTree usage : resultSetUsages) { - Tree parent = usage.parent(); - if(parent.is(Tree.Kind.ASSIGNMENT)){ - AssignmentExpressionTree assignment = (AssignmentExpressionTree) parent; - ExpressionTree expressionTree = assignment.variable(); - if(expressionTree.is(Tree.Kind.IDENTIFIER)){ - if(resultSet.equals(((IdentifierTree) expressionTree).symbol())) { - return; - } - } - } + IdentifierTree id = (IdentifierTree) expression; + Symbol statement = id.symbol(); + if(statement == null) { + return null; } - - // STEP 3 : retrieve the columns used from the resultSet object - - List usedColumns = new ArrayList<>(); - for(IdentifierTree usage : resultSetUsages) { - if(usage.parent().is(Tree.Kind.ASSIGNMENT)){ - break; - } + List usages = statement.usages(); + Symbol resultSet = null; + for(IdentifierTree usage : usages) { MethodInvocationTree methodInvocation = getMethodInvocationFromTree(usage); if(methodInvocation == null ){ continue; } - if(SQL_RESULTSET_GET_COLNAME.matches(methodInvocation)) { - ExpressionTree columnET = methodInvocation.arguments().get(0); - if(!columnET.is(Tree.Kind.STRING_LITERAL)) { - continue; - } - String column = ((LiteralTree) columnET).value(); - - column = column.toUpperCase(); - column = column.replaceAll("^['\"]", ""); - column = column.replaceAll("['\"]$", ""); - usedColumns.add(column); - } else if(SQL_RESULTSET_GET_COLID.matches(methodInvocation)) { - ExpressionTree columnET = methodInvocation.arguments().get(0); - if(!columnET.is(Tree.Kind.INT_LITERAL)) { - continue; - } - int column = Integer.parseInt(((LiteralTree) columnET).value()); - if(column > selectedColumns.size()) { - continue; + if(SQL_STATEMENT_RETRIEVE_RESULTSET.matches(methodInvocation)){ + Tree parent = methodInvocation.parent(); + if(parent.is(Tree.Kind.VARIABLE)){ + resultSet = ((VariableTree) parent).symbol(); + break; } - usedColumns.add(selectedColumns.get(column - 1)); } } - - // STEP 4 : compare selected and used columns, report issues - List differences = selectedColumns.stream() - .filter(element -> !usedColumns.contains(element)) - .collect(Collectors.toList()); - if(!differences.isEmpty()) { - reportIssue(methodInvocationTree, MESSAGERULE); - } - + return resultSet; } - + @Nullable private static MethodInvocationTree getMethodInvocationFromTree(IdentifierTree tree) { Tree parent = tree; while(parent != null && !parent.is(Tree.Kind.METHOD_INVOCATION) ){ @@ -226,43 +213,71 @@ private static MethodInvocationTree getMethodInvocationFromTree(IdentifierTree t return (MethodInvocationTree) parent; } - private static String extractValueFromVariable(IdentifierTree tree){ - Symbol symbol = tree.symbol(); + @Nullable + private static LiteralTree extractLiteralFromVariable(ExpressionTree tree){ + if(tree == null || tree instanceof LiteralTree){ + return (LiteralTree) tree; + } + if (!tree.is(Tree.Kind.IDENTIFIER)) { + return null; + } + IdentifierTree identifierTree = (IdentifierTree) tree; + Symbol symbol = identifierTree.symbol(); if(symbol == null) { return null; } - //accept this value if it's a final variable or it's a variable that is not reassigned if(!symbol.isFinal()){ - List usages = symbol.usages(); - int assignementCount = 0; - for(IdentifierTree usage : usages) { - Tree parent = usage.parent(); - if(parent.is(Tree.Kind.ASSIGNMENT)){ - assignementCount++; - } - } - if(assignementCount > 1){ - return null; - } + return null; } if(symbol.isVariableSymbol()) { VariableSymbol variableSymbol = (VariableSymbol) symbol; - Type type = variableSymbol.type(); - if(type.is("java.lang.String")) { - Tree assignement = variableSymbol.declaration(); - if(assignement.is(Tree.Kind.VARIABLE)){ - VariableTree variableTree = (VariableTree) assignement; - ExpressionTree initializer = variableTree.initializer(); - if(initializer.is(Tree.Kind.STRING_LITERAL)){ - return ((LiteralTree) initializer).value(); + Tree assignement = variableSymbol.declaration(); + if(assignement.is(Tree.Kind.VARIABLE)){ + VariableTree variableTree = (VariableTree) assignement; + ExpressionTree initializer = variableTree.initializer(); + return extractLiteralFromVariable(initializer); + } + } + return null; + } + + private static boolean isResultSetInvalid(Symbol resultSet) { + return isResultSetUsedInMethod(resultSet) + || isResultSetReassigned(resultSet); + } + + private static boolean isResultSetUsedInMethod(Symbol resultSet) { + List resultSetUsages = resultSet.usages(); + for(IdentifierTree usage : resultSetUsages) { + Tree parent = usage.parent(); + if(parent.is(Tree.Kind.ARGUMENTS)){ + return true; + } + } + return false; + } + + private static boolean isResultSetReassigned(Symbol resultSet) { + List resultSetUsages = resultSet.usages(); + for(IdentifierTree usage : resultSetUsages) { + Tree parent = usage.parent(); + if(parent.is(Tree.Kind.ASSIGNMENT)){ + AssignmentExpressionTree assignment = (AssignmentExpressionTree) parent; + ExpressionTree expressionTree = assignment.variable(); + if(expressionTree.is(Tree.Kind.IDENTIFIER)){ + if(resultSet.equals(((IdentifierTree) expressionTree).symbol())) { + return true; } } } } - return null; + return false; } static List extractSelectedSQLColumns(String query){ + if(query == null){ + return new ArrayList<>(); + } query = query.toUpperCase(); query = query.replaceAll("^['\"]", ""); query = query.replaceAll("['\"]$", ""); diff --git a/src/test/files/UseEveryColumnQueriedCompliant4.java b/src/test/files/UseEveryColumnQueriedCompliant4.java index 350b23c..0573121 100644 --- a/src/test/files/UseEveryColumnQueriedCompliant4.java +++ b/src/test/files/UseEveryColumnQueriedCompliant4.java @@ -29,6 +29,8 @@ public class UseEveryColumnQueriedCompliant4 { private static final String USER = "guest"; private static final String PASS = "guest123"; private static final String QUERY = "SELECT id, first, last, age FROM Registration"; + private static final String ID = "id"; + private static final int AGE = 4; public void callJdbc() { @@ -37,8 +39,8 @@ public void callJdbc() { ResultSet rs = stmt.executeQuery(QUERY);) { while (rs.next()) { // Display values - System.out.print("ID: " + rs.getInt("id")); - System.out.print(", Age: " + rs.getInt(4)); + System.out.print("ID: " + rs.getInt(ID)); + System.out.print(", Age: " + rs.getInt(AGE)); System.out.print(", First: " + rs.getString(2)); System.out.println(", Last: " + rs.getString("last")); } diff --git a/src/test/files/UseEveryColumnQueriedNonCompliant4.java b/src/test/files/UseEveryColumnQueriedNonCompliant4.java index 0023523..ffe7355 100644 --- a/src/test/files/UseEveryColumnQueriedNonCompliant4.java +++ b/src/test/files/UseEveryColumnQueriedNonCompliant4.java @@ -29,6 +29,7 @@ public class UseEveryColumnQueriedNonCompliant4 { private static final String USER = "guest"; private static final String PASS = "guest123"; private static final String QUERY = "SELECT id, first, last, age FROM Registration"; + private static final String ID = "id"; public void callJdbc() { @@ -37,7 +38,7 @@ public void callJdbc() { ResultSet rs = stmt.executeQuery(QUERY);) { // Noncompliant {{Avoid querying SQL columns that are not used}} while (rs.next()) { // Display values - System.out.print("ID: " + rs.getInt("id")); + System.out.print("ID: " + rs.getInt(ID)); System.out.print(", First: " + rs.getString(2)); System.out.println(", Last: " + rs.getString("last")); } diff --git a/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java b/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java index df1ab3b..61c664f 100644 --- a/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java +++ b/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java @@ -71,6 +71,7 @@ void testHasIssues4() { } @Test + @Disabled // case not handled (multiple queries with the same ResultSet object) void testHasIssues5() { CheckVerifier.newVerifier() .onFile("src/test/files/UseEveryColumnQueriedNonCompliant5.java") @@ -79,7 +80,7 @@ void testHasIssues5() { } @Test - @Disabled // case not handled yet + @Disabled // case not handled (usage of a method) void testHasIssues6() { CheckVerifier.newVerifier() .onFile("src/test/files/UseEveryColumnQueriedNonCompliant6.java") From 2b7f600c58a1bc58e835fde3ee7a1b53b92ee700 Mon Sep 17 00:00:00 2001 From: Maxime Daniel Date: Wed, 12 Jun 2024 09:09:50 +0200 Subject: [PATCH 13/32] refactor: :recycle: fix formating --- .../java/checks/UseEveryColumnQueried.java | 188 ++++++++---------- 1 file changed, 84 insertions(+), 104 deletions(-) diff --git a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java index f45fd80..2d0b660 100644 --- a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java +++ b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java @@ -48,35 +48,33 @@ public class UseEveryColumnQueried extends IssuableSubscriptionVisitor { protected static final String MESSAGERULE = "Avoid querying SQL columns that are not used"; private static final String JAVA_SQL_STATEMENT = "java.sql.Statement"; private static final String JAVA_SQL_RESULTSET = "java.sql.ResultSet"; - private static final MethodMatchers SQL_STATEMENT_DECLARE_SQL = MethodMatchers - .create() - .ofSubTypes(JAVA_SQL_STATEMENT) - //TODO : also take into account addBatch and executeBatch - .names("executeQuery", "execute", "executeUpdate", "executeLargeUpdate") - .addParametersMatcher("java.lang.String") - .build(); - private static MethodMatchers SQL_STATEMENT_RETRIEVE_RESULTSET = MethodMatchers - .create() - .ofSubTypes(JAVA_SQL_STATEMENT) - .names("executeQuery", "getResultSet") - .withAnyParameters() - .build(); - private static final MethodMatchers SQL_RESULTSET_GET_COLNAME = MethodMatchers - .create() - .ofSubTypes(JAVA_SQL_RESULTSET) - .names("getInt", "getString", "getLong", "getDouble", "getFloat", "getBoolean", "getByte", "getShort", "getBigDecimal", - "getTimestamp", "getDate", "getTime", "getObject", "getArray", "getBlob", "getClob", "getRef", "getRowId", - "getNClob", "getSQLXML", "getURL", "getNString", "getNCharacterStream", "getCharacterStream", "getAsciiStream", "getBinaryStream") - .addParametersMatcher("java.lang.String") - .build(); - private static final MethodMatchers SQL_RESULTSET_GET_COLID = MethodMatchers - .create() - .ofSubTypes(JAVA_SQL_RESULTSET) - .names("getInt", "getString", "getLong", "getDouble", "getFloat", "getBoolean", "getByte", "getShort", "getBigDecimal", - "getTimestamp", "getDate", "getTime", "getObject", "getArray", "getBlob", "getClob", "getRef", "getRowId", - "getNClob", "getSQLXML", "getURL", "getNString", "getNCharacterStream", "getCharacterStream", "getAsciiStream", "getBinaryStream") - .addParametersMatcher("int") - .build(); + private static final MethodMatchers SQL_STATEMENT_DECLARE_SQL = MethodMatchers.create() + .ofSubTypes(JAVA_SQL_STATEMENT) + // TODO : also take into account addBatch and executeBatch + .names("executeQuery", "execute", "executeUpdate", "executeLargeUpdate") + .addParametersMatcher("java.lang.String") + .build(); + private static final MethodMatchers SQL_STATEMENT_RETRIEVE_RESULTSET = MethodMatchers.create() + .ofSubTypes(JAVA_SQL_STATEMENT) + .names("executeQuery", "getResultSet") + .withAnyParameters() + .build(); + private static final MethodMatchers SQL_RESULTSET_GET_COLNAME = MethodMatchers.create() + .ofSubTypes(JAVA_SQL_RESULTSET) + .names("getInt", "getString", "getLong", "getDouble", "getFloat", "getBoolean", "getByte", "getShort", + "getBigDecimal", "getTimestamp", "getDate", "getTime", "getObject", "getArray", "getBlob", + "getClob", "getRef", "getRowId", "getNClob", "getSQLXML", "getURL", "getNString", + "getNCharacterStream", "getCharacterStream", "getAsciiStream", "getBinaryStream") + .addParametersMatcher("java.lang.String") + .build(); + private static final MethodMatchers SQL_RESULTSET_GET_COLID = MethodMatchers.create() + .ofSubTypes(JAVA_SQL_RESULTSET) + .names("getInt", "getString", "getLong", "getDouble", "getFloat", "getBoolean", "getByte", "getShort", + "getBigDecimal", "getTimestamp", "getDate", "getTime", "getObject", "getArray", "getBlob", + "getClob", "getRef", "getRowId", "getNClob", "getSQLXML", "getURL", "getNString", + "getNCharacterStream", "getCharacterStream", "getAsciiStream", "getBinaryStream") + .addParametersMatcher("int") + .build(); private static final Pattern SELECTED_COLUMNS_PATTERN = Pattern.compile("SELECT\\s+(.*)\\s+FROM\\s+.*"); @Override @@ -86,77 +84,70 @@ public List nodesToVisit() { @Override public void visitNode(Tree tree) { - MethodInvocationTree methodInvocationTree = (MethodInvocationTree) tree; - if(!SQL_STATEMENT_DECLARE_SQL.matches(methodInvocationTree)) { + if (!SQL_STATEMENT_DECLARE_SQL.matches(methodInvocationTree)) { return; } List selectedColumns = getSelectedColumns(methodInvocationTree); - if(selectedColumns.isEmpty()) { + if (selectedColumns.isEmpty()) { return; } List usedColumns = getUsedColumns(methodInvocationTree, selectedColumns); - if(usedColumns == null) { + if (usedColumns == null) { return; } List differences = selectedColumns.stream() - .filter(element -> !usedColumns.contains(element)) - .collect(Collectors.toList()); - if(!differences.isEmpty()) { + .filter(element -> !usedColumns.contains(element)) + .collect(Collectors.toList()); + if (!differences.isEmpty()) { reportIssue(methodInvocationTree, MESSAGERULE); } } private static List getSelectedColumns(MethodInvocationTree methodInvocationTree) { Arguments arguments = methodInvocationTree.arguments(); - if(arguments.isEmpty()) { + if (arguments.isEmpty()) { return new ArrayList<>(); } ExpressionTree argument = arguments.get(0); - LiteralTree litteral = extractLiteralFromVariable(argument); - if(litteral == null) { + LiteralTree literal = extractLiteralFromVariable(argument); + if (literal == null) { return new ArrayList<>(); } - String query = litteral.value(); - List selectedColumns = extractSelectedSQLColumns(query); - return selectedColumns; + String query = literal.value(); + return extractSelectedSQLColumns(query); } @Nullable - private static List getUsedColumns(MethodInvocationTree methodInvocationTree, List selectedColumns){ + private static List getUsedColumns(MethodInvocationTree methodInvocationTree, List selectedColumns) { Symbol resultSet = getResultSetNode(methodInvocationTree); - - if(resultSet == null || isResultSetInvalid(resultSet)) { + if (resultSet == null || isResultSetInvalid(resultSet)) { return null; } - List usedColumns = new ArrayList<>(); List resultSetUsages = resultSet.usages(); - for(IdentifierTree usage : resultSetUsages) { - if(usage.parent().is(Tree.Kind.ASSIGNMENT)){ + for (IdentifierTree usage : resultSetUsages) { + if (usage.parent().is(Tree.Kind.ASSIGNMENT)) { break; } MethodInvocationTree methodInvocation = getMethodInvocationFromTree(usage); - if(methodInvocation == null ){ - continue; - } - if(methodInvocation.arguments().isEmpty()){ + if (methodInvocation == null || methodInvocation.arguments().isEmpty()) { continue; } ExpressionTree parameter = methodInvocation.arguments().get(0); LiteralTree columnGot = extractLiteralFromVariable(parameter); - if(columnGot == null){ + if (columnGot == null) { continue; } String column; String value = columnGot.value(); - if(SQL_RESULTSET_GET_COLNAME.matches(methodInvocation) && columnGot.is(Tree.Kind.STRING_LITERAL)) { - column = value.toUpperCase(); - column = column.replaceAll("^['\"]", ""); - column = column.replaceAll("['\"]$", ""); - } else if(SQL_RESULTSET_GET_COLID.matches(methodInvocation) && columnGot.is(Tree.Kind.INT_LITERAL)) { + if (SQL_RESULTSET_GET_COLNAME.matches(methodInvocation) && columnGot.is(Tree.Kind.STRING_LITERAL)) { + column = value.toUpperCase() + .replaceAll("^['\"]", "") + .replaceAll("['\"]$", ""); + } else if (SQL_RESULTSET_GET_COLID.matches(methodInvocation) && columnGot.is(Tree.Kind.INT_LITERAL)) { int columnId = Integer.parseInt(value); - if(columnId > selectedColumns.size()) { + if (columnId > selectedColumns.size()) { break; } column = selectedColumns.get(columnId - 1); @@ -171,51 +162,47 @@ private static List getUsedColumns(MethodInvocationTree methodInvocation @Nullable private static Symbol getResultSetNode(MethodInvocationTree methodInvocationTree) { ExpressionTree et = methodInvocationTree.methodSelect(); - if(!et.is(Tree.Kind.MEMBER_SELECT)) { + if (!et.is(Tree.Kind.MEMBER_SELECT)) { return null; } MemberSelectExpressionTree mset = (MemberSelectExpressionTree) et; ExpressionTree expression = mset.expression(); - if(!expression.is(Tree.Kind.IDENTIFIER)) { + if (!expression.is(Tree.Kind.IDENTIFIER)) { return null; } IdentifierTree id = (IdentifierTree) expression; Symbol statement = id.symbol(); - if(statement == null) { + if (statement == null) { return null; } List usages = statement.usages(); Symbol resultSet = null; - for(IdentifierTree usage : usages) { + for (IdentifierTree usage : usages) { MethodInvocationTree methodInvocation = getMethodInvocationFromTree(usage); - if(methodInvocation == null ){ + if (methodInvocation == null || !SQL_STATEMENT_RETRIEVE_RESULTSET.matches(methodInvocation)) { continue; } - if(SQL_STATEMENT_RETRIEVE_RESULTSET.matches(methodInvocation)){ - Tree parent = methodInvocation.parent(); - if(parent.is(Tree.Kind.VARIABLE)){ - resultSet = ((VariableTree) parent).symbol(); - break; - } + Tree parent = methodInvocation.parent(); + if (parent.is(Tree.Kind.VARIABLE)) { + resultSet = ((VariableTree) parent).symbol(); + break; } } return resultSet; } + @Nullable private static MethodInvocationTree getMethodInvocationFromTree(IdentifierTree tree) { Tree parent = tree; - while(parent != null && !parent.is(Tree.Kind.METHOD_INVOCATION) ){ + while (parent != null && !parent.is(Tree.Kind.METHOD_INVOCATION)) { parent = parent.parent(); } - if(parent == null){ - return null; - } return (MethodInvocationTree) parent; } @Nullable - private static LiteralTree extractLiteralFromVariable(ExpressionTree tree){ - if(tree == null || tree instanceof LiteralTree){ + private static LiteralTree extractLiteralFromVariable(ExpressionTree tree) { + if (tree == null || tree instanceof LiteralTree) { return (LiteralTree) tree; } if (!tree.is(Tree.Kind.IDENTIFIER)) { @@ -223,34 +210,29 @@ private static LiteralTree extractLiteralFromVariable(ExpressionTree tree){ } IdentifierTree identifierTree = (IdentifierTree) tree; Symbol symbol = identifierTree.symbol(); - if(symbol == null) { + if (symbol == null || !symbol.isFinal() || !symbol.isVariableSymbol()) { return null; } - if(!symbol.isFinal()){ + VariableSymbol variableSymbol = (VariableSymbol) symbol; + Tree assignment = variableSymbol.declaration(); + if (!assignment.is(Tree.Kind.VARIABLE)) { return null; } - if(symbol.isVariableSymbol()) { - VariableSymbol variableSymbol = (VariableSymbol) symbol; - Tree assignement = variableSymbol.declaration(); - if(assignement.is(Tree.Kind.VARIABLE)){ - VariableTree variableTree = (VariableTree) assignement; - ExpressionTree initializer = variableTree.initializer(); - return extractLiteralFromVariable(initializer); - } - } - return null; + VariableTree variableTree = (VariableTree) assignment; + ExpressionTree initializer = variableTree.initializer(); + return extractLiteralFromVariable(initializer); } private static boolean isResultSetInvalid(Symbol resultSet) { - return isResultSetUsedInMethod(resultSet) - || isResultSetReassigned(resultSet); + return isResultSetUsedInMethod(resultSet) + || isResultSetReassigned(resultSet); } private static boolean isResultSetUsedInMethod(Symbol resultSet) { List resultSetUsages = resultSet.usages(); - for(IdentifierTree usage : resultSetUsages) { + for (IdentifierTree usage : resultSetUsages) { Tree parent = usage.parent(); - if(parent.is(Tree.Kind.ARGUMENTS)){ + if (parent.is(Tree.Kind.ARGUMENTS)) { return true; } } @@ -259,28 +241,27 @@ private static boolean isResultSetUsedInMethod(Symbol resultSet) { private static boolean isResultSetReassigned(Symbol resultSet) { List resultSetUsages = resultSet.usages(); - for(IdentifierTree usage : resultSetUsages) { + for (IdentifierTree usage : resultSetUsages) { Tree parent = usage.parent(); - if(parent.is(Tree.Kind.ASSIGNMENT)){ + if (parent.is(Tree.Kind.ASSIGNMENT)) { AssignmentExpressionTree assignment = (AssignmentExpressionTree) parent; ExpressionTree expressionTree = assignment.variable(); - if(expressionTree.is(Tree.Kind.IDENTIFIER)){ - if(resultSet.equals(((IdentifierTree) expressionTree).symbol())) { - return true; - } + if (expressionTree.is(Tree.Kind.IDENTIFIER) + && resultSet.equals(((IdentifierTree) expressionTree).symbol())) { + return true; } } } return false; } - static List extractSelectedSQLColumns(String query){ - if(query == null){ + static List extractSelectedSQLColumns(String query) { + if (query == null) { return new ArrayList<>(); } - query = query.toUpperCase(); - query = query.replaceAll("^['\"]", ""); - query = query.replaceAll("['\"]$", ""); + query = query.toUpperCase() + .replaceAll("^['\"]", "") + .replaceAll("['\"]$", ""); List columns = new ArrayList<>(); Matcher matcher = SELECTED_COLUMNS_PATTERN.matcher(query); if (matcher.matches()) { @@ -291,5 +272,4 @@ static List extractSelectedSQLColumns(String query){ } return columns; } - } From a6938a441a8c96e6f292d7ab365d62fabd2d11bb Mon Sep 17 00:00:00 2001 From: David DE CARVALHO Date: Wed, 12 Jun 2024 22:06:52 +0200 Subject: [PATCH 14/32] comment on TODO_DDC.md for docker port pb --- _TODOs_DDC.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/_TODOs_DDC.md b/_TODOs_DDC.md index d318d99..85e767d 100644 --- a/_TODOs_DDC.md +++ b/_TODOs_DDC.md @@ -7,4 +7,5 @@ - check usefulness - upgrade versions - enable github `dependabot` to create automatically PR with version upgrades of dependencides (when all dependencies will be ok) -- ménage dans les branches de dev (local et remote) \ No newline at end of file +- ménage dans les branches de dev (local et remote) +- docker-compose : ":9000" (génération port aléatoire pour l'IHM + repérage pour IHM) au lieu de "9000:9000" si erreur lors du démarrage "Error response from daemon: Ports are not available: exposing port TCP 0.0.0.0:9000 -> 0.0.0.0:0: listen tcp 0.0.0.0:9000: bind: address already in use" \ No newline at end of file From 42006efff8c87a3fa79eb85ff5739cac57403c3f Mon Sep 17 00:00:00 2001 From: David DE CARVALHO Date: Fri, 19 Jul 2024 16:59:36 +0200 Subject: [PATCH 15/32] add 10.6.0 SonarQube version + update docker port managment --- CHANGELOG.md | 2 ++ docker-compose.yml | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ce62a8..c1bd27c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Check + update for SonarQube 10.6.0 compatibility + ### Deleted ## [1.6.1] - 2024-05-15 diff --git a/docker-compose.yml b/docker-compose.yml index ee7dc87..6cc49eb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,10 +1,10 @@ version: "3.3" services: sonar: - image: sonarqube:10.5.1-community + image: sonarqube:10.6.0-community container_name: sonar_ecocode_java ports: - - "9000:9000" + - ":9000" networks: - sonarnet depends_on: From 688cffc2151534216acdefb1f2ee6fb17ed9ee0b Mon Sep 17 00:00:00 2001 From: David DE CARVALHO Date: Sun, 21 Jul 2024 15:59:13 +0200 Subject: [PATCH 16/32] [ISSUE 60] update compatibility to SonarQube 10.6.0 --- CHANGELOG.md | 3 ++- Dockerfile | 11 +++++++++-- README.md | 2 +- docker-compose.yml | 21 +++++++++++++++------ 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1bd27c..c63808b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Check + update for SonarQube 10.6.0 compatibility +- [#60](https://github.com/green-code-initiative/ecoCode-java/issues/60) Check + update for SonarQube 10.6.0 compatibility +- refactoring docker system ### Deleted diff --git a/Dockerfile b/Dockerfile index a5a0fc7..421eacd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,16 @@ -FROM maven:3-openjdk-11-slim AS builder +ARG MAVEN_BUILDER=3-openjdk-17-slim +ARG SONARQUBE_VERSION=10.6.0-community + +FROM maven:${MAVEN_BUILDER} AS builder COPY . /usr/src/ecocode WORKDIR /usr/src/ecocode +COPY src src/ +COPY pom.xml tool_build.sh ./ + RUN ./tool_build.sh -FROM sonarqube:10.5.1-community +FROM sonarqube:${SONARQUBE_VERSION} COPY --from=builder /usr/src/ecocode/target/ecocode-*.jar /opt/sonarqube/extensions/plugins/ +USER sonarqube diff --git a/README.md b/README.md index 7fab3c0..a28c058 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ Ready to use binaries are available [from GitHub](https://github.com/green-code- | Plugin version | SonarQube version | Java version | |----------------|---------------------|--------------| -| 1.5.+ | 9.4.+ LTS to 10.5.1 | 11 / 17 | +| 1.6.+ | 9.4.+ LTS to 10.6.0 | 11 / 17 | > Compatibility table of versions lower than 1.4.+ are available from the > main [ecoCode repository](https://github.com/green-code-initiative/ecoCode#-plugins-version-compatibility). diff --git a/docker-compose.yml b/docker-compose.yml index 6cc49eb..e84e389 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,23 +1,27 @@ -version: "3.3" +name: sonarqube_ecocode_java + services: sonar: - image: sonarqube:10.6.0-community + build: . container_name: sonar_ecocode_java ports: - ":9000" networks: - sonarnet depends_on: - - db + db: + condition: service_healthy environment: SONAR_JDBC_USERNAME: sonar SONAR_JDBC_PASSWORD: sonar SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonarqube SONAR_ES_BOOTSTRAP_CHECKS_DISABLE: 'true' + env_file: + - path: ./.default.docker.env + required: true + - path: ./.override.docker.env + required: false volumes: - - type: bind - source: ./target/ecocode-java-plugin-1.6.2-SNAPSHOT.jar - target: /opt/sonarqube/extensions/plugins/ecocode-java-plugin-1.6.2-SNAPSHOT.jar - "extensions:/opt/sonarqube/extensions" - "logs:/opt/sonarqube/logs" - "data:/opt/sonarqube/data" @@ -34,6 +38,11 @@ services: POSTGRES_PASSWORD: sonar POSTGRES_DB: sonarqube PGDATA: pg_data:/var/lib/postgresql/data/pgdata + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U sonar -d sonarqube" ] + interval: 5s + timeout: 5s + retries: 5 networks: sonarnet: From 0316bc39d8281d7ac45cea542df517de4cbb039b Mon Sep 17 00:00:00 2001 From: David DE CARVALHO Date: Sun, 21 Jul 2024 16:35:22 +0200 Subject: [PATCH 17/32] upgrade ecocode-rules-spec to 1.6.2 --- CHANGELOG.md | 1 + pom.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c63808b..1347d35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#60](https://github.com/green-code-initiative/ecoCode-java/issues/60) Check + update for SonarQube 10.6.0 compatibility - refactoring docker system +- upgrade ecocode-rules-specifications to 1.6.2 ### Deleted diff --git a/pom.xml b/pom.xml index fa0c7e9..6d2e373 100644 --- a/pom.xml +++ b/pom.xml @@ -67,7 +67,7 @@ 1.7 - 1.5.1 + 1.6.2 From 13aa2fa2aae9fb31896426ba126f5a8bc7f936ac Mon Sep 17 00:00:00 2001 From: David DE CARVALHO Date: Sun, 21 Jul 2024 16:37:30 +0200 Subject: [PATCH 18/32] prepare 1.6.2 : update CHANGELOG --- CHANGELOG.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1347d35..554bf66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,12 +11,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +### Deleted + +## [1.6.2] - 2024-07-21 + +### Changed + - [#60](https://github.com/green-code-initiative/ecoCode-java/issues/60) Check + update for SonarQube 10.6.0 compatibility - refactoring docker system - upgrade ecocode-rules-specifications to 1.6.2 -### Deleted - ## [1.6.1] - 2024-05-15 ### Changed From 660711d34208c0e0edef05dd0485c74918a53133 Mon Sep 17 00:00:00 2001 From: David DE CARVALHO Date: Sun, 21 Jul 2024 16:37:53 +0200 Subject: [PATCH 19/32] [maven-release-plugin] prepare release 1.6.2 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 6d2e373..2a0e11d 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ io.ecocode ecocode-java-plugin - 1.6.2-SNAPSHOT + 1.6.2 sonar-plugin @@ -30,7 +30,7 @@ scm:git:https://github.com/green-code-initiative/ecocode-java scm:git:https://github.com/green-code-initiative/ecocode-java https://github.com/green-code-initiative/ecocode-java - HEAD + 1.6.2 From 49b32fe451f91fc0b1d9ff6b5bb0ee36512a83c1 Mon Sep 17 00:00:00 2001 From: David DE CARVALHO Date: Sun, 21 Jul 2024 16:37:53 +0200 Subject: [PATCH 20/32] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 2a0e11d..1c657cb 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ io.ecocode ecocode-java-plugin - 1.6.2 + 1.6.3-SNAPSHOT sonar-plugin @@ -30,7 +30,7 @@ scm:git:https://github.com/green-code-initiative/ecocode-java scm:git:https://github.com/green-code-initiative/ecocode-java https://github.com/green-code-initiative/ecocode-java - 1.6.2 + HEAD From 4b849b5e3b23b9ec734d8746766b69b06e87df4d Mon Sep 17 00:00:00 2001 From: David DE CARVALHO Date: Tue, 23 Jul 2024 18:21:11 +0200 Subject: [PATCH 21/32] correction of docker env --- .default.docker.env | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .default.docker.env diff --git a/.default.docker.env b/.default.docker.env new file mode 100644 index 0000000..01ee4f2 --- /dev/null +++ b/.default.docker.env @@ -0,0 +1,5 @@ +# Set default Sonarqube environment variables used by docker-compose +# You can override these envvars by creating a '.override.docker.env' file +# For available envvars list, see https://docs.sonarsource.com/sonarqube/latest/setup-and-upgrade/configure-and-operate-a-server/environment-variables/ + +SONAR_LOG_LEVEL_WEB=INFO From 688974fc63950eef6771e1f2b66911403bb497d9 Mon Sep 17 00:00:00 2001 From: David DE CARVALHO Date: Fri, 26 Jul 2024 21:27:54 +0200 Subject: [PATCH 22/32] [PR 49] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 554bf66..855c493 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- [#49](https://github.com/green-code-initiative/ecoCode-java/pull/49) Add test to ensure all Rules are registered + ### Deleted ## [1.6.2] - 2024-07-21 From 30b131f5892b6774d58a9fc438d0e8d51a29b0c8 Mon Sep 17 00:00:00 2001 From: Maxime DANIEL Date: Wed, 7 Aug 2024 16:04:24 +0200 Subject: [PATCH 23/32] remove executeUpdate --- .../greencodeinitiative/java/checks/UseEveryColumnQueried.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java index 2d0b660..24d084f 100644 --- a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java +++ b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java @@ -51,7 +51,7 @@ public class UseEveryColumnQueried extends IssuableSubscriptionVisitor { private static final MethodMatchers SQL_STATEMENT_DECLARE_SQL = MethodMatchers.create() .ofSubTypes(JAVA_SQL_STATEMENT) // TODO : also take into account addBatch and executeBatch - .names("executeQuery", "execute", "executeUpdate", "executeLargeUpdate") + .names("executeQuery", "execute") .addParametersMatcher("java.lang.String") .build(); private static final MethodMatchers SQL_STATEMENT_RETRIEVE_RESULTSET = MethodMatchers.create() From 647df32ddeabc4770f7a96c1a4b6f9c98e9f466a Mon Sep 17 00:00:00 2001 From: Maxime DANIEL Date: Wed, 7 Aug 2024 16:57:03 +0200 Subject: [PATCH 24/32] add some comments --- .../java/checks/UseEveryColumnQueried.java | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java index 24d084f..c3a16b1 100644 --- a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java +++ b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java @@ -50,7 +50,6 @@ public class UseEveryColumnQueried extends IssuableSubscriptionVisitor { private static final String JAVA_SQL_RESULTSET = "java.sql.ResultSet"; private static final MethodMatchers SQL_STATEMENT_DECLARE_SQL = MethodMatchers.create() .ofSubTypes(JAVA_SQL_STATEMENT) - // TODO : also take into account addBatch and executeBatch .names("executeQuery", "execute") .addParametersMatcher("java.lang.String") .build(); @@ -82,20 +81,56 @@ public List nodesToVisit() { return Arrays.asList(Kind.METHOD_INVOCATION); } + /** + * How this rule works : + * We start from the method invocation that declares the SQL query ( stmt.executeQuery("SELECT ... FROM ...") ) + * the selected columns are directly extracted from the parameters of this method invocation + * We explore the stmt object to find where the method invocation that returns the ResultSet object + * finally we explore all invocations of this ResultSet object to list all the column used. + * the selected and used columns are compared, and an issue is reported if columns are selected but not used. + * + * + * stmt.execute("SELECT ... FROM ...") or stmt.executeQuery(...) + * | | + * | ----> Selected Columns + * [Statement Object] + * | + * | + * v + * res = stmt.getResultSet() or stmt.executeQuery(...) + * | + * | + * [ResultSet Object] + * | + * | + * v + * res.getInt(...) or any column extraction method + * | + * ----> Used Column + * + */ @Override public void visitNode(Tree tree) { MethodInvocationTree methodInvocationTree = (MethodInvocationTree) tree; if (!SQL_STATEMENT_DECLARE_SQL.matches(methodInvocationTree)) { return; } + + // extraction of the selected columns List selectedColumns = getSelectedColumns(methodInvocationTree); if (selectedColumns.isEmpty()) { return; } + + // extraction of the used columns List usedColumns = getUsedColumns(methodInvocationTree, selectedColumns); if (usedColumns == null) { + // usedColumns is an empty List when there is no ResultSet or it is never read from + // usedColumns is null when the ResultSet was redefined or passed in a method. return; } + + // if there are selected columns that are not used in the code, report the issue List differences = selectedColumns.stream() .filter(element -> !usedColumns.contains(element)) .collect(Collectors.toList()); @@ -121,7 +156,10 @@ private static List getSelectedColumns(MethodInvocationTree methodInvoca @Nullable private static List getUsedColumns(MethodInvocationTree methodInvocationTree, List selectedColumns) { Symbol resultSet = getResultSetNode(methodInvocationTree); - if (resultSet == null || isResultSetInvalid(resultSet)) { + if (resultSet == null) { + return new ArrayList<>() + } + if(isResultSetInvalid(resultSet)){ return null; } List usedColumns = new ArrayList<>(); From 50259b0d44d6299746d4511256b10ca06e80e145 Mon Sep 17 00:00:00 2001 From: Maxime DANIEL Date: Mon, 19 Aug 2024 10:27:33 +0200 Subject: [PATCH 25/32] small refactor and add comments --- .../java/checks/UseEveryColumnQueried.java | 137 ++++++++++++------ 1 file changed, 91 insertions(+), 46 deletions(-) diff --git a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java index c3a16b1..316e460 100644 --- a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java +++ b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java @@ -122,14 +122,18 @@ public void visitNode(Tree tree) { return; } - // extraction of the used columns - List usedColumns = getUsedColumns(methodInvocationTree, selectedColumns); - if (usedColumns == null) { - // usedColumns is an empty List when there is no ResultSet or it is never read from - // usedColumns is null when the ResultSet was redefined or passed in a method. + // get the ResultSet object and check it's validity + Symbol resultSet = getResultSetNode(methodInvocationTree); + if (resultSet == null) { + return; + } + if(isResultSetInvalid(resultSet)){ return; } + // extraction of the used columns + List usedColumns = getUsedColumns(resultSet, selectedColumns); + // if there are selected columns that are not used in the code, report the issue List differences = selectedColumns.stream() .filter(element -> !usedColumns.contains(element)) @@ -140,31 +144,31 @@ public void visitNode(Tree tree) { } private static List getSelectedColumns(MethodInvocationTree methodInvocationTree) { + // get the first argument of the query definition method Arguments arguments = methodInvocationTree.arguments(); if (arguments.isEmpty()) { return new ArrayList<>(); } ExpressionTree argument = arguments.get(0); + // get the contents of the string in this first parameters LiteralTree literal = extractLiteralFromVariable(argument); if (literal == null) { return new ArrayList<>(); } String query = literal.value(); + //get the list of selected columns from this string return extractSelectedSQLColumns(query); } - @Nullable - private static List getUsedColumns(MethodInvocationTree methodInvocationTree, List selectedColumns) { - Symbol resultSet = getResultSetNode(methodInvocationTree); - if (resultSet == null) { - return new ArrayList<>() - } - if(isResultSetInvalid(resultSet)){ - return null; - } + /** + * returns a list of used columns from a resultset object and a list of the selected columns + */ + private static List getUsedColumns(Symbol resultSet, List selectedColumns) { + // iterate across all usages of the ResultSet List usedColumns = new ArrayList<>(); List resultSetUsages = resultSet.usages(); for (IdentifierTree usage : resultSetUsages) { + // check this usage is an assignement, and the method parameters if (usage.parent().is(Tree.Kind.ASSIGNMENT)) { break; } @@ -172,6 +176,7 @@ private static List getUsedColumns(MethodInvocationTree methodInvocation if (methodInvocation == null || methodInvocation.arguments().isEmpty()) { continue; } + // get the value of the first parameter ExpressionTree parameter = methodInvocation.arguments().get(0); LiteralTree columnGot = extractLiteralFromVariable(parameter); if (columnGot == null) { @@ -179,10 +184,12 @@ private static List getUsedColumns(MethodInvocationTree methodInvocation } String column; String value = columnGot.value(); + // if this first parameter is a string, clean up and use as is for used column name if (SQL_RESULTSET_GET_COLNAME.matches(methodInvocation) && columnGot.is(Tree.Kind.STRING_LITERAL)) { column = value.toUpperCase() .replaceAll("^['\"]", "") .replaceAll("['\"]$", ""); + // if this first parameter is an int, use as and id for used column name } else if (SQL_RESULTSET_GET_COLID.matches(methodInvocation) && columnGot.is(Tree.Kind.INT_LITERAL)) { int columnId = Integer.parseInt(value); if (columnId > selectedColumns.size()) { @@ -197,8 +204,46 @@ private static List getUsedColumns(MethodInvocationTree methodInvocation return usedColumns; } + /** + * returns the litteral assigned to a variable var + * var has to be either a litteral itself or a final variable + * returns null if the ExpressionTree is not a variable, + * if the variable is not final, or if the variable has not been initialized + */ + @Nullable + private static LiteralTree extractLiteralFromVariable(ExpressionTree tree) { + if (tree instanceof LiteralTree) { + return (LiteralTree) tree; + } + if (!tree.is(Tree.Kind.IDENTIFIER)) { + return null; + } + IdentifierTree identifierTree = (IdentifierTree) tree; + Symbol symbol = identifierTree.symbol(); + if (symbol == null || !symbol.isFinal() || !symbol.isVariableSymbol()) { + return null; + } + VariableSymbol variableSymbol = (VariableSymbol) symbol; + Tree assignment = variableSymbol.declaration(); + if (!assignment.is(Tree.Kind.VARIABLE)) { + return null; + } + VariableTree variableTree = (VariableTree) assignment; + ExpressionTree initializer = variableTree.initializer(); + if (initializer instanceof LiteralTree) { + return (LiteralTree) initializer; + } + return null; + } + + /** + * get the ResultSet Object assigned from the result of the retrieve resultset method + * from the sql declaration method (via the shared stmt object) + * stmt.execute(...) -> rs = stmt.getResultSet() + */ @Nullable private static Symbol getResultSetNode(MethodInvocationTree methodInvocationTree) { + // get the Statement object on witch the method is called ExpressionTree et = methodInvocationTree.methodSelect(); if (!et.is(Tree.Kind.MEMBER_SELECT)) { return null; @@ -213,13 +258,16 @@ private static Symbol getResultSetNode(MethodInvocationTree methodInvocationTree if (statement == null) { return null; } + // iterate over all usages of this Statement object List usages = statement.usages(); Symbol resultSet = null; for (IdentifierTree usage : usages) { + // does this usage of the Statement object match SQL_STATEMENT_RETRIEVE_RESULTSET ? MethodInvocationTree methodInvocation = getMethodInvocationFromTree(usage); if (methodInvocation == null || !SQL_STATEMENT_RETRIEVE_RESULTSET.matches(methodInvocation)) { continue; } + // if so end the search, we have found our resultSet object Tree parent = methodInvocation.parent(); if (parent.is(Tree.Kind.VARIABLE)) { resultSet = ((VariableTree) parent).symbol(); @@ -229,6 +277,10 @@ private static Symbol getResultSetNode(MethodInvocationTree methodInvocationTree return resultSet; } + /** + * unpacks a chain call to get the method invocation node + * example : this.object.chain.method() -> method() + */ @Nullable private static MethodInvocationTree getMethodInvocationFromTree(IdentifierTree tree) { Tree parent = tree; @@ -238,37 +290,23 @@ private static MethodInvocationTree getMethodInvocationFromTree(IdentifierTree t return (MethodInvocationTree) parent; } - @Nullable - private static LiteralTree extractLiteralFromVariable(ExpressionTree tree) { - if (tree == null || tree instanceof LiteralTree) { - return (LiteralTree) tree; - } - if (!tree.is(Tree.Kind.IDENTIFIER)) { - return null; - } - IdentifierTree identifierTree = (IdentifierTree) tree; - Symbol symbol = identifierTree.symbol(); - if (symbol == null || !symbol.isFinal() || !symbol.isVariableSymbol()) { - return null; - } - VariableSymbol variableSymbol = (VariableSymbol) symbol; - Tree assignment = variableSymbol.declaration(); - if (!assignment.is(Tree.Kind.VARIABLE)) { - return null; - } - VariableTree variableTree = (VariableTree) assignment; - ExpressionTree initializer = variableTree.initializer(); - return extractLiteralFromVariable(initializer); - } - + /** + * checks the two conditions that make a ResultSet object invalid, + * and would stop the search for used columns because of side effects + * - the ResultSet object being passed in a method + * - the ResultSet object being reassigned + */ private static boolean isResultSetInvalid(Symbol resultSet) { - return isResultSetUsedInMethod(resultSet) - || isResultSetReassigned(resultSet); + return isObjectUsedInMethodParameters(resultSet) + || isObjectReassigned(resultSet); } - private static boolean isResultSetUsedInMethod(Symbol resultSet) { - List resultSetUsages = resultSet.usages(); - for (IdentifierTree usage : resultSetUsages) { + /** + * checks if an object is used as a parameter a method + */ + private static boolean isObjectUsedInMethodParameters(Symbol obj) { + List usages = obj.usages(); + for (IdentifierTree usage : usages) { Tree parent = usage.parent(); if (parent.is(Tree.Kind.ARGUMENTS)) { return true; @@ -277,15 +315,18 @@ private static boolean isResultSetUsedInMethod(Symbol resultSet) { return false; } - private static boolean isResultSetReassigned(Symbol resultSet) { - List resultSetUsages = resultSet.usages(); - for (IdentifierTree usage : resultSetUsages) { + /** + * checks if an object is reassigned + */ + private static boolean isObjectReassigned(Symbol obj) { + List usages = obj.usages(); + for (IdentifierTree usage : usages) { Tree parent = usage.parent(); if (parent.is(Tree.Kind.ASSIGNMENT)) { AssignmentExpressionTree assignment = (AssignmentExpressionTree) parent; ExpressionTree expressionTree = assignment.variable(); if (expressionTree.is(Tree.Kind.IDENTIFIER) - && resultSet.equals(((IdentifierTree) expressionTree).symbol())) { + && obj.equals(((IdentifierTree) expressionTree).symbol())) { return true; } } @@ -293,6 +334,10 @@ private static boolean isResultSetReassigned(Symbol resultSet) { return false; } + /** + * extract from a SQL query in the form of "SELECT X, Y AS Z FROM TABLE ..." + * a list of all the column names and aliases (X and Z) without whitespace and in uppercase + */ static List extractSelectedSQLColumns(String query) { if (query == null) { return new ArrayList<>(); From 753cdc24b3539996e4445ac1ea88bec3574da526 Mon Sep 17 00:00:00 2001 From: jycr Date: Sun, 1 Sep 2024 15:25:10 +0200 Subject: [PATCH 26/32] [ecoCode#336] Adds Maven Wrapper This Maven Wrapper is an easy way to ensure a developer has everything necessary to build this project. Executed command to add wrapper to project: ```sh mvn wrapper:wrapper -Dmaven=3.9.9 ``` --- .gitattributes | 6 +- .gitignore | 1 + .mvn/wrapper/maven-wrapper.properties | 19 ++ CHANGELOG.md | 1 + mvnw | 259 ++++++++++++++++++++++++++ mvnw.cmd | 149 +++++++++++++++ 6 files changed, 434 insertions(+), 1 deletion(-) create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100755 mvnw create mode 100644 mvnw.cmd diff --git a/.gitattributes b/.gitattributes index 12b960c..9039a78 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,4 +4,8 @@ # Ensure BAT files will always be checked out with CRLFs (regardless of the # OS they were checked out on). -*.bat text eol=crlf \ No newline at end of file +*.bat text eol=crlf + +# Ensure BAT files will always be checked out with CRLFs (regardless of the +# OS they were checked out on). +*.cmd text eol=crlf diff --git a/.gitignore b/.gitignore index 67cc592..8554f83 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Ignore all files and folders starting with ".", except a few exceptions .* +!.mvn/ !.gitignore !.gitattributes !.github/ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..d58dfb7 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/CHANGELOG.md b/CHANGELOG.md index 855c493..535e14f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - [#49](https://github.com/green-code-initiative/ecoCode-java/pull/49) Add test to ensure all Rules are registered +- [green-code-initiative/ecoCode#336](https://github.com/green-code-initiative/ecoCode/issues/336) [Adds Maven Wrapper](https://github.com/green-code-initiative/ecoCode-java/pull/67) ### Deleted diff --git a/mvnw b/mvnw new file mode 100755 index 0000000..19529dd --- /dev/null +++ b/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..249bdf3 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" From b1abd7bc8044f4189a66784446c0d4cb114fea8e Mon Sep 17 00:00:00 2001 From: jycr Date: Sun, 1 Sep 2024 15:28:09 +0200 Subject: [PATCH 27/32] Migrates scripts to use Maven Wrapper (to ensure consistency of Maven version used across different environments) --- .github/workflows/_BACKUP_manual_release.yml | 6 +++--- .github/workflows/build.yml | 4 ++-- .github/workflows/tag_release.yml | 2 +- tool_build.sh | 2 +- tool_compile.sh | 2 +- tool_release_1_prepare.sh | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/_BACKUP_manual_release.yml b/.github/workflows/_BACKUP_manual_release.yml index 90818a5..0930754 100644 --- a/.github/workflows/_BACKUP_manual_release.yml +++ b/.github/workflows/_BACKUP_manual_release.yml @@ -31,9 +31,9 @@ jobs: git config user.name 'github-actions[bot]' git config user.email '' - name: Maven release - run: mvn release:prepare -B -ff -DtagNameFormat=@{project.version} + run: ./mvnw release:prepare -B -ff -DtagNameFormat=@{project.version} - name: Maven release clean - run: mvn release:clean + run: ./mvnw release:clean - name: Get last TAG run: echo "LAST_TAG=$(git tag --sort=-version:refname | head -n 1)" >> $GITHUB_ENV - name: Extract release notes @@ -44,7 +44,7 @@ jobs: with: ref: ${{ env.LAST_TAG }} - name: Build project - run: mvn -e -B clean package -DskipTests + run: ./mvnw -e -B clean package -DskipTests - name: Create release id: create_release uses: actions/create-release@v1 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 60db5ba..b6e2d7e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,7 +38,7 @@ jobs: restore-keys: ${{ runner.os }}-m2 - name: Verify - run: mvn -e -B verify + run: ./mvnw -e -B verify - name: Set up JDK 17 uses: actions/setup-java@v3 @@ -57,4 +57,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: mvn -e -B org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=green-code-initiative_ecoCode-java + run: ./mvnw -e -B org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=green-code-initiative_ecoCode-java diff --git a/.github/workflows/tag_release.yml b/.github/workflows/tag_release.yml index b405ace..75e6e62 100644 --- a/.github/workflows/tag_release.yml +++ b/.github/workflows/tag_release.yml @@ -29,7 +29,7 @@ jobs: id: extract-release-notes uses: ffurrer2/extract-release-notes@v1 - name: Build project - run: mvn -e -B clean package -DskipTests + run: ./mvnw -e -B clean package -DskipTests - name: Create release id: create_release uses: actions/create-release@v1 diff --git a/tool_build.sh b/tool_build.sh index bfac031..a5f6a09 100755 --- a/tool_build.sh +++ b/tool_build.sh @@ -1,3 +1,3 @@ #!/usr/bin/env sh -mvn clean package -DskipTests +./mvnw clean package -DskipTests diff --git a/tool_compile.sh b/tool_compile.sh index 3d3e0d8..9d4da3c 100755 --- a/tool_compile.sh +++ b/tool_compile.sh @@ -1,3 +1,3 @@ #!/usr/bin/env sh -mvn clean compile +./mvnw clean compile diff --git a/tool_release_1_prepare.sh b/tool_release_1_prepare.sh index e87b946..7b996e3 100755 --- a/tool_release_1_prepare.sh +++ b/tool_release_1_prepare.sh @@ -5,9 +5,9 @@ ### # creation of 2 commits with release and next SNAPSHOT -mvn release:prepare -B -ff -DpushChanges=false -DtagNameFormat=@{project.version} +./mvnw release:prepare -B -ff -DpushChanges=false -DtagNameFormat=@{project.version} sleep 2 # clean temporary files -mvn release:clean +./mvnw release:clean From 3a5825772b20b953d14e9c9d64dfedd2c192a55a Mon Sep 17 00:00:00 2001 From: jycr Date: Thu, 30 May 2024 19:47:45 +0200 Subject: [PATCH 28/32] feat: Adds "ecoCode way" profile. This profile aggregate all implemented rules by this plugin. This profile makes it easier to test ecoCode rules --- CHANGELOG.md | 2 + .../java/JavaEcoCodeWayProfile.java | 40 +++++++++++++++ .../java/JavaRulesDefinition.java | 2 +- .../java/ecoCode_way_profile.json | 21 ++++++++ .../java/JavaEcoCodeWayProfileTest.java | 51 +++++++++++++++++++ 5 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 src/main/java/fr/greencodeinitiative/java/JavaEcoCodeWayProfile.java create mode 100644 src/main/resources/fr/greencodeinitiative/java/ecoCode_way_profile.json create mode 100644 src/test/java/fr/greencodeinitiative/java/JavaEcoCodeWayProfileTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 855c493..79b9ac3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- [#59](https://github.com/green-code-initiative/ecoCode-java/pull/59) Add builtin profile `ecoCode way` to aggregate all implemented ecoCode rules by this plugin + ### Changed - [#49](https://github.com/green-code-initiative/ecoCode-java/pull/49) Add test to ensure all Rules are registered diff --git a/src/main/java/fr/greencodeinitiative/java/JavaEcoCodeWayProfile.java b/src/main/java/fr/greencodeinitiative/java/JavaEcoCodeWayProfile.java new file mode 100644 index 0000000..d24fc82 --- /dev/null +++ b/src/main/java/fr/greencodeinitiative/java/JavaEcoCodeWayProfile.java @@ -0,0 +1,40 @@ +/* + * 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 . + */ +package fr.greencodeinitiative.java; + +import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; +import org.sonarsource.analyzer.commons.BuiltInQualityProfileJsonLoader; + +import static fr.greencodeinitiative.java.JavaRulesDefinition.LANGUAGE; +import static fr.greencodeinitiative.java.JavaRulesDefinition.REPOSITORY_KEY; + +public final class JavaEcoCodeWayProfile implements BuiltInQualityProfilesDefinition { + static final String PROFILE_NAME = "ecoCode way"; + static final String PROFILE_PATH = JavaEcoCodeWayProfile.class.getPackageName().replace('.', '/') + "/ecoCode_way_profile.json"; + + @Override + public void define(Context context) { + NewBuiltInQualityProfile ecoCodeProfile = context.createBuiltInQualityProfile(PROFILE_NAME, LANGUAGE); + loadProfile(ecoCodeProfile); + ecoCodeProfile.done(); + } + + private void loadProfile(NewBuiltInQualityProfile profile) { + BuiltInQualityProfileJsonLoader.load(profile, REPOSITORY_KEY, PROFILE_PATH); + } +} diff --git a/src/main/java/fr/greencodeinitiative/java/JavaRulesDefinition.java b/src/main/java/fr/greencodeinitiative/java/JavaRulesDefinition.java index 958edc8..f9e03e3 100644 --- a/src/main/java/fr/greencodeinitiative/java/JavaRulesDefinition.java +++ b/src/main/java/fr/greencodeinitiative/java/JavaRulesDefinition.java @@ -31,7 +31,7 @@ public class JavaRulesDefinition implements RulesDefinition { private static final String RESOURCE_BASE_PATH = "io/ecocode/rules/java"; private static final String NAME = "ecoCode"; - private static final String LANGUAGE = "java"; + static final String LANGUAGE = "java"; static final String REPOSITORY_KEY = "ecocode-java"; private final SonarRuntime sonarRuntime; diff --git a/src/main/resources/fr/greencodeinitiative/java/ecoCode_way_profile.json b/src/main/resources/fr/greencodeinitiative/java/ecoCode_way_profile.json new file mode 100644 index 0000000..88e381a --- /dev/null +++ b/src/main/resources/fr/greencodeinitiative/java/ecoCode_way_profile.json @@ -0,0 +1,21 @@ +{ + "name": "ecoCode way", + "language": "java", + "ruleKeys": [ + "EC1", + "EC2", + "EC3", + "EC5", + "EC27", + "EC28", + "EC32", + "EC67", + "EC69", + "EC72", + "EC74", + "EC76", + "EC77", + "EC78", + "EC79" + ] +} diff --git a/src/test/java/fr/greencodeinitiative/java/JavaEcoCodeWayProfileTest.java b/src/test/java/fr/greencodeinitiative/java/JavaEcoCodeWayProfileTest.java new file mode 100644 index 0000000..0d75016 --- /dev/null +++ b/src/test/java/fr/greencodeinitiative/java/JavaEcoCodeWayProfileTest.java @@ -0,0 +1,51 @@ +/* + * 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 . + */ +package fr.greencodeinitiative.java; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; +import org.sonar.check.Rule; + +import static fr.greencodeinitiative.java.JavaCheckRegistrarTest.getDefinedRules; +import static fr.greencodeinitiative.java.JavaEcoCodeWayProfile.PROFILE_NAME; +import static fr.greencodeinitiative.java.JavaEcoCodeWayProfile.PROFILE_PATH; +import static fr.greencodeinitiative.java.JavaRulesDefinition.LANGUAGE; +import static org.assertj.core.api.Assertions.assertThat; + +class JavaEcoCodeWayProfileTest { + @Test + void should_create_ecocode_profile() { + BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); + + JavaEcoCodeWayProfile definition = new JavaEcoCodeWayProfile(); + definition.define(context); + + BuiltInQualityProfilesDefinition.BuiltInQualityProfile profile = context.profile(LANGUAGE, PROFILE_NAME); + + assertThat(profile.language()).isEqualTo(LANGUAGE); + assertThat(profile.name()).isEqualTo(PROFILE_NAME); + List definedRuleIds = getDefinedRules().stream().map(c -> c.getAnnotation(Rule.class).key()).collect(Collectors.toList()); + assertThat(profile.rules()) + .describedAs("All implemented rules must be declared in '%s' profile file: %s", PROFILE_NAME, PROFILE_PATH) + .map(BuiltInQualityProfilesDefinition.BuiltInActiveRule::ruleKey) + .containsExactlyInAnyOrderElementsOf(definedRuleIds); + } +} From 55781acd65f821cdb183bfeedf10987b4d39387f Mon Sep 17 00:00:00 2001 From: David DE CARVALHO Date: Fri, 6 Sep 2024 16:33:06 +0200 Subject: [PATCH 29/32] update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c60f73..9bec331 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - [#49](https://github.com/green-code-initiative/ecoCode-java/pull/49) Add test to ensure all Rules are registered -- [green-code-initiative/ecoCode#336](https://github.com/green-code-initiative/ecoCode/issues/336) [Adds Maven Wrapper](https://github.com/green-code-initiative/ecoCode-java/pull/67) +- [#336](https://github.com/green-code-initiative/ecoCode/issues/336) [Adds Maven Wrapper](https://github.com/green-code-initiative/ecoCode-java/pull/67) ### Deleted From d0fe4d2bc3547cf9ffc661562e9a44c4e10704c8 Mon Sep 17 00:00:00 2001 From: Maxime DANIEL Date: Mon, 9 Sep 2024 09:11:02 +0000 Subject: [PATCH 30/32] Add rule to registrar and correct ID --- .../java/fr/greencodeinitiative/java/JavaCheckRegistrar.java | 4 +++- .../java/checks/UseEveryColumnQueried.java | 2 +- .../fr/greencodeinitiative/java/ecoCode_way_profile.json | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/fr/greencodeinitiative/java/JavaCheckRegistrar.java b/src/main/java/fr/greencodeinitiative/java/JavaCheckRegistrar.java index 50ada18..b90a262 100644 --- a/src/main/java/fr/greencodeinitiative/java/JavaCheckRegistrar.java +++ b/src/main/java/fr/greencodeinitiative/java/JavaCheckRegistrar.java @@ -35,6 +35,7 @@ import fr.greencodeinitiative.java.checks.InitializeBufferWithAppropriateSize; import fr.greencodeinitiative.java.checks.NoFunctionCallWhenDeclaringForLoop; import fr.greencodeinitiative.java.checks.OptimizeReadFileExceptions; +import fr.greencodeinitiative.java.checks.UseEveryColumnQueried; import org.sonar.plugins.java.api.CheckRegistrar; import org.sonar.plugins.java.api.JavaCheck; import org.sonarsource.api.sonarlint.SonarLintSide; @@ -62,7 +63,8 @@ public class JavaCheckRegistrar implements CheckRegistrar { InitializeBufferWithAppropriateSize.class, AvoidSetConstantInBatchUpdate.class, FreeResourcesOfAutoCloseableInterface.class, - AvoidMultipleIfElseStatement.class + AvoidMultipleIfElseStatement.class, + UseEveryColumnQueried.class ); /** diff --git a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java index 316e460..57d78d5 100644 --- a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java +++ b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java @@ -42,7 +42,7 @@ import org.sonar.plugins.java.api.tree.VariableTree; import org.sonar.plugins.java.api.tree.Tree.Kind; -@Rule(key = "1044") +@Rule(key = "EC1044") public class UseEveryColumnQueried extends IssuableSubscriptionVisitor { protected static final String MESSAGERULE = "Avoid querying SQL columns that are not used"; diff --git a/src/main/resources/fr/greencodeinitiative/java/ecoCode_way_profile.json b/src/main/resources/fr/greencodeinitiative/java/ecoCode_way_profile.json index 88e381a..b2f127c 100644 --- a/src/main/resources/fr/greencodeinitiative/java/ecoCode_way_profile.json +++ b/src/main/resources/fr/greencodeinitiative/java/ecoCode_way_profile.json @@ -16,6 +16,7 @@ "EC76", "EC77", "EC78", - "EC79" + "EC79", + "EC1044" ] } From 0e76371d0a8b524ea018bea35e784d8fb040df1d Mon Sep 17 00:00:00 2001 From: Maxime DANIEL Date: Mon, 9 Sep 2024 09:12:25 +0000 Subject: [PATCH 31/32] Improve test names and add select * case --- .../java/checks/UseEveryColumnQueried.java | 5 ++ .../AttributeQueryCompliant.java} | 6 +- .../AttributeQueryNonCompliant.java} | 6 +- .../LitteralQueryCompliant.java} | 6 +- .../LitteralQueryNonCompliant.java} | 6 +- .../MultipleQueriesCompliant.java} | 6 +- .../MultipleQueriesNonCompliant.java} | 21 ++++--- .../UseEveryColumnQueried/SelectStar.java | 55 +++++++++++++++++++ ...eColumnIdsAndNameAttributesCompliant.java} | 6 +- ...lumnIdsAndNameAttributesNonCompliant.java} | 6 +- .../UseMethodCompliant.java} | 6 +- .../UseMethodNonCompliant.java} | 6 +- .../UseEveryColumnQueriedCompliant3.java | 50 ----------------- .../UseEveryColumnQueriedNonCompliant3.java | 49 ----------------- .../checks/UseEveryColumnQueriedTest.java | 35 +++++------- 15 files changed, 131 insertions(+), 138 deletions(-) rename src/test/files/{UseEveryColumnQueriedCompliant1.java => UseEveryColumnQueried/AttributeQueryCompliant.java} (90%) rename src/test/files/{UseEveryColumnQueriedNonCompliant1.java => UseEveryColumnQueried/AttributeQueryNonCompliant.java} (90%) rename src/test/files/{UseEveryColumnQueriedCompliant2.java => UseEveryColumnQueried/LitteralQueryCompliant.java} (90%) rename src/test/files/{UseEveryColumnQueriedNonCompliant2.java => UseEveryColumnQueried/LitteralQueryNonCompliant.java} (90%) rename src/test/files/{UseEveryColumnQueriedCompliant5.java => UseEveryColumnQueried/MultipleQueriesCompliant.java} (92%) rename src/test/files/{UseEveryColumnQueriedNonCompliant5.java => UseEveryColumnQueried/MultipleQueriesNonCompliant.java} (92%) create mode 100644 src/test/files/UseEveryColumnQueried/SelectStar.java rename src/test/files/{UseEveryColumnQueriedCompliant4.java => UseEveryColumnQueried/UseColumnIdsAndNameAttributesCompliant.java} (89%) rename src/test/files/{UseEveryColumnQueriedNonCompliant4.java => UseEveryColumnQueried/UseColumnIdsAndNameAttributesNonCompliant.java} (89%) rename src/test/files/{UseEveryColumnQueriedCompliant6.java => UseEveryColumnQueried/UseMethodCompliant.java} (92%) rename src/test/files/{UseEveryColumnQueriedNonCompliant6.java => UseEveryColumnQueried/UseMethodNonCompliant.java} (92%) delete mode 100644 src/test/files/UseEveryColumnQueriedCompliant3.java delete mode 100644 src/test/files/UseEveryColumnQueriedNonCompliant3.java diff --git a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java index 57d78d5..9eac513 100644 --- a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java +++ b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java @@ -122,6 +122,11 @@ public void visitNode(Tree tree) { return; } + //if selected columns includes "*", stop the search + if (selectedColumns.contains("*")) { + return; + } + // get the ResultSet object and check it's validity Symbol resultSet = getResultSetNode(methodInvocationTree); if (resultSet == null) { diff --git a/src/test/files/UseEveryColumnQueriedCompliant1.java b/src/test/files/UseEveryColumnQueried/AttributeQueryCompliant.java similarity index 90% rename from src/test/files/UseEveryColumnQueriedCompliant1.java rename to src/test/files/UseEveryColumnQueried/AttributeQueryCompliant.java index e81a11c..c12e375 100644 --- a/src/test/files/UseEveryColumnQueriedCompliant1.java +++ b/src/test/files/UseEveryColumnQueried/AttributeQueryCompliant.java @@ -23,7 +23,11 @@ import java.sql.SQLException; import java.sql.Statement; -public class UseEveryColumnQueriedCompliant1 { +/** + * This is the nominal test case, where the SQL query is an attribute of the class. + * All Fields are accessed, so no issue is raised + */ +public class AttributeQueryCompliant { private static final String DB_URL = "jdbc:mysql://localhost/TEST"; private static final String USER = "guest"; diff --git a/src/test/files/UseEveryColumnQueriedNonCompliant1.java b/src/test/files/UseEveryColumnQueried/AttributeQueryNonCompliant.java similarity index 90% rename from src/test/files/UseEveryColumnQueriedNonCompliant1.java rename to src/test/files/UseEveryColumnQueried/AttributeQueryNonCompliant.java index 1c40dbb..e42ff48 100644 --- a/src/test/files/UseEveryColumnQueriedNonCompliant1.java +++ b/src/test/files/UseEveryColumnQueried/AttributeQueryNonCompliant.java @@ -23,7 +23,11 @@ import java.sql.SQLException; import java.sql.Statement; -public class UseEveryColumnQueriedNonCompliant1 { +/** + * This is the nominal test case, where the SQL query is an attribute of the class. + * One field is not accesed, so an issue is raised + */ +public class AttributeQueryNonCompliant { private static final String DB_URL = "jdbc:mysql://localhost/TEST"; private static final String USER = "guest"; diff --git a/src/test/files/UseEveryColumnQueriedCompliant2.java b/src/test/files/UseEveryColumnQueried/LitteralQueryCompliant.java similarity index 90% rename from src/test/files/UseEveryColumnQueriedCompliant2.java rename to src/test/files/UseEveryColumnQueried/LitteralQueryCompliant.java index 3b45c2f..6146dfa 100644 --- a/src/test/files/UseEveryColumnQueriedCompliant2.java +++ b/src/test/files/UseEveryColumnQueried/LitteralQueryCompliant.java @@ -23,7 +23,11 @@ import java.sql.SQLException; import java.sql.Statement; -public class UseEveryColumnQueriedCompliant2 { +/** + * In this test case, the query is a litteral string directly inserted in the method + * All Fields are accessed, so no issue is raised + */ +public class LitteralQueryCompliant { private static final String DB_URL = "jdbc:mysql://localhost/TEST"; private static final String USER = "guest"; diff --git a/src/test/files/UseEveryColumnQueriedNonCompliant2.java b/src/test/files/UseEveryColumnQueried/LitteralQueryNonCompliant.java similarity index 90% rename from src/test/files/UseEveryColumnQueriedNonCompliant2.java rename to src/test/files/UseEveryColumnQueried/LitteralQueryNonCompliant.java index 8da60f0..476233d 100644 --- a/src/test/files/UseEveryColumnQueriedNonCompliant2.java +++ b/src/test/files/UseEveryColumnQueried/LitteralQueryNonCompliant.java @@ -23,7 +23,11 @@ import java.sql.SQLException; import java.sql.Statement; -public class UseEveryColumnQueriedNonCompliant2 { +/** + * In this test case, the query is a litteral string directly inserted in the method + * One field is not accesed, so an issue is raised + */ +public class LitteralQueryNonCompliant { private static final String DB_URL = "jdbc:mysql://localhost/TEST"; private static final String USER = "guest"; diff --git a/src/test/files/UseEveryColumnQueriedCompliant5.java b/src/test/files/UseEveryColumnQueried/MultipleQueriesCompliant.java similarity index 92% rename from src/test/files/UseEveryColumnQueriedCompliant5.java rename to src/test/files/UseEveryColumnQueried/MultipleQueriesCompliant.java index 4f5f68c..94f7a29 100644 --- a/src/test/files/UseEveryColumnQueriedCompliant5.java +++ b/src/test/files/UseEveryColumnQueried/MultipleQueriesCompliant.java @@ -23,7 +23,11 @@ import java.sql.SQLException; import java.sql.Statement; -public class UseEveryColumnQueriedCompliant5 { +/** + * In this test case, multiple queries are done using the same Statement Object. + * All Fields are accessed, so no issue is raised + */ +public class MultipleQueriesCompliant { private static final String DB_URL = "jdbc:mysql://localhost/TEST"; private static final String USER = "guest"; diff --git a/src/test/files/UseEveryColumnQueriedNonCompliant5.java b/src/test/files/UseEveryColumnQueried/MultipleQueriesNonCompliant.java similarity index 92% rename from src/test/files/UseEveryColumnQueriedNonCompliant5.java rename to src/test/files/UseEveryColumnQueried/MultipleQueriesNonCompliant.java index 959bc80..f7cf12c 100644 --- a/src/test/files/UseEveryColumnQueriedNonCompliant5.java +++ b/src/test/files/UseEveryColumnQueried/MultipleQueriesNonCompliant.java @@ -1,10 +1,3 @@ -package fr.greencodeinitiative.java.checks; - -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; /* * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs * Copyright © 2023 Green Code Initiative (https://www.ecocode.io) @@ -22,7 +15,19 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -public class UseEveryColumnQueriedNonCompliant5 { +package fr.greencodeinitiative.java.checks; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +/** + * In this test case, multiple queries are done using the same Statement Object. + * One field is not accesed, so an issue is raised + */ +public class MultipleQueriesNonCompliant { private static final String DB_URL = "jdbc:mysql://localhost/TEST"; private static final String USER = "guest"; diff --git a/src/test/files/UseEveryColumnQueried/SelectStar.java b/src/test/files/UseEveryColumnQueried/SelectStar.java new file mode 100644 index 0000000..84fe348 --- /dev/null +++ b/src/test/files/UseEveryColumnQueried/SelectStar.java @@ -0,0 +1,55 @@ +/* + * 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 . + */ +package fr.greencodeinitiative.java.checks; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +/** + * In this test case, we use a "select * from" statement + * Since we can't know what columns exist, we can't know if all columns are being used, no issue is raised + * "select * from" is bad practice but is already covered by EC74 + */ +public class SelectStar { + + private static final String DB_URL = "jdbc:mysql://localhost/TEST"; + private static final String USER = "guest"; + private static final String PASS = "guest123"; + private static final String QUERY = "SELECT * FROM Registration"; + + public void callJdbc() { + + try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(QUERY);) { + while (rs.next()) { + // Display values + System.out.print("ID: " + rs.getInt("id")); + System.out.print(", Age: " + rs.getInt("age")); + System.out.print(", First: " + rs.getString("first")); + System.out.println(", Last: " + rs.getString("last")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + } +} diff --git a/src/test/files/UseEveryColumnQueriedCompliant4.java b/src/test/files/UseEveryColumnQueried/UseColumnIdsAndNameAttributesCompliant.java similarity index 89% rename from src/test/files/UseEveryColumnQueriedCompliant4.java rename to src/test/files/UseEveryColumnQueried/UseColumnIdsAndNameAttributesCompliant.java index 0573121..03ac224 100644 --- a/src/test/files/UseEveryColumnQueriedCompliant4.java +++ b/src/test/files/UseEveryColumnQueried/UseColumnIdsAndNameAttributesCompliant.java @@ -23,7 +23,11 @@ import java.sql.SQLException; import java.sql.Statement; -public class UseEveryColumnQueriedCompliant4 { +/** + * In this test case, columns are accesed by IDs and names, some of them being in final variables + * All Fields are accessed, so no issue is raised + */ +public class UseColumnIdsAndNameAttributesCompliant { private static final String DB_URL = "jdbc:mysql://localhost/TEST"; private static final String USER = "guest"; diff --git a/src/test/files/UseEveryColumnQueriedNonCompliant4.java b/src/test/files/UseEveryColumnQueried/UseColumnIdsAndNameAttributesNonCompliant.java similarity index 89% rename from src/test/files/UseEveryColumnQueriedNonCompliant4.java rename to src/test/files/UseEveryColumnQueried/UseColumnIdsAndNameAttributesNonCompliant.java index ffe7355..c7972d4 100644 --- a/src/test/files/UseEveryColumnQueriedNonCompliant4.java +++ b/src/test/files/UseEveryColumnQueried/UseColumnIdsAndNameAttributesNonCompliant.java @@ -23,7 +23,11 @@ import java.sql.SQLException; import java.sql.Statement; -public class UseEveryColumnQueriedNonCompliant4 { +/** + * In this test case, columns are accesed by IDs and names, some of them being in final variables + * One field is not accesed, so an issue is raised + */ +public class UseColumnIdsAndNameAttributesNonCompliant { private static final String DB_URL = "jdbc:mysql://localhost/TEST"; private static final String USER = "guest"; diff --git a/src/test/files/UseEveryColumnQueriedCompliant6.java b/src/test/files/UseEveryColumnQueried/UseMethodCompliant.java similarity index 92% rename from src/test/files/UseEveryColumnQueriedCompliant6.java rename to src/test/files/UseEveryColumnQueried/UseMethodCompliant.java index a3b28e8..db7ae3e 100644 --- a/src/test/files/UseEveryColumnQueriedCompliant6.java +++ b/src/test/files/UseEveryColumnQueried/UseMethodCompliant.java @@ -23,7 +23,11 @@ import java.sql.SQLException; import java.sql.Statement; -public class UseEveryColumnQueriedCompliant6 { +/** + * In this test case, the ResultSet is passed through a method + * All Fields are accessed, so no issue is raised + */ +public class UseMethodCompliant { private static final String DB_URL = "jdbc:mysql://localhost/TEST"; private static final String USER = "guest"; diff --git a/src/test/files/UseEveryColumnQueriedNonCompliant6.java b/src/test/files/UseEveryColumnQueried/UseMethodNonCompliant.java similarity index 92% rename from src/test/files/UseEveryColumnQueriedNonCompliant6.java rename to src/test/files/UseEveryColumnQueried/UseMethodNonCompliant.java index 86d6a86..559c06b 100644 --- a/src/test/files/UseEveryColumnQueriedNonCompliant6.java +++ b/src/test/files/UseEveryColumnQueried/UseMethodNonCompliant.java @@ -23,7 +23,11 @@ import java.sql.SQLException; import java.sql.Statement; -public class UseEveryColumnQueriedNonCompliant6 { +/** + * In this test case, the ResultSet is passed through a method + * One field is not accesed, so an issue is raised + */ +public class UseMethodNonCompliant { private static final String DB_URL = "jdbc:mysql://localhost/TEST"; private static final String USER = "guest"; diff --git a/src/test/files/UseEveryColumnQueriedCompliant3.java b/src/test/files/UseEveryColumnQueriedCompliant3.java deleted file mode 100644 index e2a5082..0000000 --- a/src/test/files/UseEveryColumnQueriedCompliant3.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 . - */ -package fr.greencodeinitiative.java.checks; - -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - -public class UseEveryColumnQueriedCompliant3 { - - private static final String DB_URL = "jdbc:mysql://localhost/TEST"; - private static final String USER = "guest"; - private static final String PASS = "guest123"; - private static final String QUERY = "SELECT id, first, last, age FROM Registration"; - - public void callJdbc() { - - try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); - Statement stmt = conn.createStatement(); - ResultSet rs = stmt.executeQuery(QUERY);) { - while (rs.next()) { - // Display values - System.out.print("ID: " + rs.getInt(1)); - System.out.print(", Age: " + rs.getInt(4)); - System.out.print(", First: " + rs.getString(2)); - System.out.println(", Last: " + rs.getString(3)); - } - } catch (SQLException e) { - e.printStackTrace(); - } - - } -} diff --git a/src/test/files/UseEveryColumnQueriedNonCompliant3.java b/src/test/files/UseEveryColumnQueriedNonCompliant3.java deleted file mode 100644 index 50afcc5..0000000 --- a/src/test/files/UseEveryColumnQueriedNonCompliant3.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 . - */ -package fr.greencodeinitiative.java.checks; - -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - -public class UseEveryColumnQueriedNonCompliant3 { - - private static final String DB_URL = "jdbc:mysql://localhost/TEST"; - private static final String USER = "guest"; - private static final String PASS = "guest123"; - private static final String QUERY = "SELECT id, first, last, age FROM Registration"; - - public void callJdbc() { - - try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); - Statement stmt = conn.createStatement(); - ResultSet rs = stmt.executeQuery(QUERY);) { // Noncompliant {{Avoid querying SQL columns that are not used}} - while (rs.next()) { - // Display values - System.out.print("ID: " + rs.getInt(1)); - System.out.print(", First: " + rs.getString(2)); - System.out.println(", Last: " + rs.getString(3)); - } - } catch (SQLException e) { - e.printStackTrace(); - } - - } -} diff --git a/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java b/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java index 61c664f..edf2b76 100644 --- a/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java +++ b/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java @@ -41,7 +41,7 @@ void testExtractSelectedSQLColumns(){ @Test void testHasIssues1() { CheckVerifier.newVerifier() - .onFile("src/test/files/UseEveryColumnQueriedNonCompliant1.java") + .onFile("src/test/files/UseEveryColumnQueried/AttributeQueryNonCompliant.java") .withCheck(new UseEveryColumnQueried()) .verifyIssues(); } @@ -49,7 +49,7 @@ void testHasIssues1() { @Test void testHasIssues2() { CheckVerifier.newVerifier() - .onFile("src/test/files/UseEveryColumnQueriedNonCompliant2.java") + .onFile("src/test/files/UseEveryColumnQueried/LitteralQueryNonCompliant.java") .withCheck(new UseEveryColumnQueried()) .verifyIssues(); } @@ -57,43 +57,34 @@ void testHasIssues2() { @Test void testHasIssues3() { CheckVerifier.newVerifier() - .onFile("src/test/files/UseEveryColumnQueriedNonCompliant3.java") - .withCheck(new UseEveryColumnQueried()) - .verifyIssues(); - } - - @Test - void testHasIssues4() { - CheckVerifier.newVerifier() - .onFile("src/test/files/UseEveryColumnQueriedNonCompliant4.java") + .onFile("src/test/files/UseEveryColumnQueried/UseColumnIdsAndNameAttributesNonCompliant.java") .withCheck(new UseEveryColumnQueried()) .verifyIssues(); } @Test @Disabled // case not handled (multiple queries with the same ResultSet object) - void testHasIssues5() { + void testHasIssues4() { CheckVerifier.newVerifier() - .onFile("src/test/files/UseEveryColumnQueriedNonCompliant5.java") + .onFile("src/test/files/UseEveryColumnQueried/MultipleQueriesNonCompliant.java") .withCheck(new UseEveryColumnQueried()) .verifyIssues(); } @Test @Disabled // case not handled (usage of a method) - void testHasIssues6() { + void testHasIssues5() { CheckVerifier.newVerifier() - .onFile("src/test/files/UseEveryColumnQueriedNonCompliant6.java") + .onFile("src/test/files/UseEveryColumnQueried/UseMethodNonCompliant.java") .withCheck(new UseEveryColumnQueried()) .verifyIssues(); } - @Test void testHasNoIssues1() { CheckVerifier.newVerifier() - .onFile("src/test/files/UseEveryColumnQueriedCompliant1.java") + .onFile("src/test/files/UseEveryColumnQueried/AttributeQueryCompliant.java") .withCheck(new UseEveryColumnQueried()) .verifyNoIssues(); } @@ -101,7 +92,7 @@ void testHasNoIssues1() { @Test void testHasNoIssues2() { CheckVerifier.newVerifier() - .onFile("src/test/files/UseEveryColumnQueriedCompliant2.java") + .onFile("src/test/files/UseEveryColumnQueried/LitteralQueryCompliant.java") .withCheck(new UseEveryColumnQueried()) .verifyNoIssues(); } @@ -109,7 +100,7 @@ void testHasNoIssues2() { @Test void testHasNoIssues3() { CheckVerifier.newVerifier() - .onFile("src/test/files/UseEveryColumnQueriedCompliant3.java") + .onFile("src/test/files/UseEveryColumnQueried/UseColumnIdsAndNameAttributesCompliant.java") .withCheck(new UseEveryColumnQueried()) .verifyNoIssues(); } @@ -117,7 +108,7 @@ void testHasNoIssues3() { @Test void testHasNoIssues4() { CheckVerifier.newVerifier() - .onFile("src/test/files/UseEveryColumnQueriedCompliant4.java") + .onFile("src/test/files/UseEveryColumnQueried/MultipleQueriesCompliant.java") .withCheck(new UseEveryColumnQueried()) .verifyNoIssues(); } @@ -125,7 +116,7 @@ void testHasNoIssues4() { @Test void testHasNoIssues5() { CheckVerifier.newVerifier() - .onFile("src/test/files/UseEveryColumnQueriedCompliant5.java") + .onFile("src/test/files/UseEveryColumnQueried/UseMethodCompliant.java") .withCheck(new UseEveryColumnQueried()) .verifyNoIssues(); } @@ -133,7 +124,7 @@ void testHasNoIssues5() { @Test void testHasNoIssues6() { CheckVerifier.newVerifier() - .onFile("src/test/files/UseEveryColumnQueriedCompliant6.java") + .onFile("src/test/files/UseEveryColumnQueried/SelectStar.java") .withCheck(new UseEveryColumnQueried()) .verifyNoIssues(); } From b8a7654b6c49b5ffab05fae3f49f0e28e08011a4 Mon Sep 17 00:00:00 2001 From: Maxime DANIEL Date: Mon, 9 Sep 2024 09:22:55 +0000 Subject: [PATCH 32/32] improve ResultSet get method detection --- .../java/checks/UseEveryColumnQueried.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java index 9eac513..7e85e11 100644 --- a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java +++ b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java @@ -60,18 +60,12 @@ public class UseEveryColumnQueried extends IssuableSubscriptionVisitor { .build(); private static final MethodMatchers SQL_RESULTSET_GET_COLNAME = MethodMatchers.create() .ofSubTypes(JAVA_SQL_RESULTSET) - .names("getInt", "getString", "getLong", "getDouble", "getFloat", "getBoolean", "getByte", "getShort", - "getBigDecimal", "getTimestamp", "getDate", "getTime", "getObject", "getArray", "getBlob", - "getClob", "getRef", "getRowId", "getNClob", "getSQLXML", "getURL", "getNString", - "getNCharacterStream", "getCharacterStream", "getAsciiStream", "getBinaryStream") + .name(n -> n.startsWith("get")) .addParametersMatcher("java.lang.String") .build(); private static final MethodMatchers SQL_RESULTSET_GET_COLID = MethodMatchers.create() .ofSubTypes(JAVA_SQL_RESULTSET) - .names("getInt", "getString", "getLong", "getDouble", "getFloat", "getBoolean", "getByte", "getShort", - "getBigDecimal", "getTimestamp", "getDate", "getTime", "getObject", "getArray", "getBlob", - "getClob", "getRef", "getRowId", "getNClob", "getSQLXML", "getURL", "getNString", - "getNCharacterStream", "getCharacterStream", "getAsciiStream", "getBinaryStream") + .name(n -> n.startsWith("get")) .addParametersMatcher("int") .build(); private static final Pattern SELECTED_COLUMNS_PATTERN = Pattern.compile("SELECT\\s+(.*)\\s+FROM\\s+.*");