From e1d5a775e4c9e81523cea3f88bf61ad719a67087 Mon Sep 17 00:00:00 2001 From: Erwan Le Forestier Date: Tue, 18 Aug 2026 16:48:13 +0200 Subject: [PATCH 1/5] SONARPY-4539 Ignore version IP values --- .../sonar/python/checks/HardcodedIPCheck.java | 49 ++++++++++++++++++- .../src/test/resources/checks/hardcodedIP.py | 7 +++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/python-checks/src/main/java/org/sonar/python/checks/HardcodedIPCheck.java b/python-checks/src/main/java/org/sonar/python/checks/HardcodedIPCheck.java index f9c569b7e8..6b351df052 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/HardcodedIPCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/HardcodedIPCheck.java @@ -23,6 +23,12 @@ import javax.annotation.Nullable; import org.sonar.check.Rule; import org.sonar.plugins.python.api.PythonSubscriptionCheck; +import org.sonar.plugins.python.api.tree.AnnotatedAssignment; +import org.sonar.plugins.python.api.tree.AssignmentStatement; +import org.sonar.plugins.python.api.tree.Expression; +import org.sonar.plugins.python.api.tree.ExpressionList; +import org.sonar.plugins.python.api.tree.Name; +import org.sonar.plugins.python.api.tree.ParenthesizedExpression; import org.sonar.plugins.python.api.tree.StringLiteral; import org.sonar.plugins.python.api.tree.Tree; import org.sonar.python.checks.utils.Expressions; @@ -63,7 +69,7 @@ public class HardcodedIPCheck extends PythonSubscriptionCheck { public void initialize(Context context) { context.registerSyntaxNodeConsumer(Tree.Kind.STRING_LITERAL, ctx -> { StringLiteral stringLiteral = (StringLiteral) ctx.syntaxNode(); - if (isMultilineString(stringLiteral)) { + if (isMultilineString(stringLiteral) || isVersionLiteral(stringLiteral)) { return; } String content = Expressions.unescape(stringLiteral); @@ -88,6 +94,47 @@ public void initialize(Context context) { }); } + /** + * Checks whether a literal is directly assigned to the conventional version variable. + * @param stringLiteral literal to inspect + * @return whether the literal is a direct {@code __version__} value + */ + private static boolean isVersionLiteral(StringLiteral stringLiteral) { + Expression assignedValue = stringLiteral; + while (assignedValue.parent() instanceof ParenthesizedExpression parenthesizedExpression) { + assignedValue = parenthesizedExpression; + } + Tree parent = assignedValue.parent(); + if (parent instanceof AssignmentStatement assignment) { + return assignment.assignedValue() == assignedValue && hasVersionName(assignment); + } + return parent instanceof AnnotatedAssignment assignment + && assignment.assignedValue() == assignedValue + && isVersionName(assignment.variable()); + } + + /** + * Checks whether an assignment has exactly one {@code __version__} target. + * @param assignment assignment to inspect + * @return whether the assignment has the conventional version target + */ + private static boolean hasVersionName(AssignmentStatement assignment) { + if (assignment.lhsExpressions().size() != 1) { + return false; + } + ExpressionList lhsExpressions = assignment.lhsExpressions().get(0); + return lhsExpressions.expressions().size() == 1 && isVersionName(lhsExpressions.expressions().get(0)); + } + + /** + * Checks whether an expression is the conventional version variable. + * @param expression expression to inspect + * @return whether the expression is named {@code __version__} + */ + private static boolean isVersionName(Expression expression) { + return expression instanceof Name name && "__version__".equals(name.name()); + } + private static boolean isMultilineString(StringLiteral pyStringLiteralTree) { return pyStringLiteralTree.stringElements().size() > 1; } diff --git a/python-checks/src/test/resources/checks/hardcodedIP.py b/python-checks/src/test/resources/checks/hardcodedIP.py index ea29346e0d..f24f559a87 100644 --- a/python-checks/src/test/resources/checks/hardcodedIP.py +++ b/python-checks/src/test/resources/checks/hardcodedIP.py @@ -22,6 +22,13 @@ fileName = "v0.0.1.200__do_something.sql" # Compliant - suffixed and prefixed version = "1.0.0.0-1" # Compliant - suffixed +__version__ = "26.8.0.1" # Compliant +__version__ = ("26.8.0.1") # Compliant +__version__: str = "26.8.0.1" # Compliant +version = "26.8.0.1" # Noncompliant +VERSION = "26.8.0.1" # Noncompliant +__version__ = version = "26.8.0.1" # Noncompliant + "1080:0:0:0:8:800:200C:417A" # Noncompliant {{Make sure using this hardcoded IP address "1080:0:0:0:8:800:200C:417A" is safe here.}} "[1080::8:800:200C:417A]" # Noncompliant "::800:200C:417A" # Noncompliant From 99febabb77796b1f8c1fa14ee665c099e84b80c1 Mon Sep 17 00:00:00 2001 From: Erwan Le Forestier Date: Tue, 18 Aug 2026 17:01:01 +0200 Subject: [PATCH 2/5] SONARPY-4539 Handle parenthesized version targets --- .../main/java/org/sonar/python/checks/HardcodedIPCheck.java | 3 +++ python-checks/src/test/resources/checks/hardcodedIP.py | 1 + 2 files changed, 4 insertions(+) diff --git a/python-checks/src/main/java/org/sonar/python/checks/HardcodedIPCheck.java b/python-checks/src/main/java/org/sonar/python/checks/HardcodedIPCheck.java index 6b351df052..a5ecc92d3f 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/HardcodedIPCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/HardcodedIPCheck.java @@ -132,6 +132,9 @@ private static boolean hasVersionName(AssignmentStatement assignment) { * @return whether the expression is named {@code __version__} */ private static boolean isVersionName(Expression expression) { + while (expression instanceof ParenthesizedExpression parenthesizedExpression) { + expression = parenthesizedExpression.expression(); + } return expression instanceof Name name && "__version__".equals(name.name()); } diff --git a/python-checks/src/test/resources/checks/hardcodedIP.py b/python-checks/src/test/resources/checks/hardcodedIP.py index f24f559a87..0f5f7df186 100644 --- a/python-checks/src/test/resources/checks/hardcodedIP.py +++ b/python-checks/src/test/resources/checks/hardcodedIP.py @@ -25,6 +25,7 @@ __version__ = "26.8.0.1" # Compliant __version__ = ("26.8.0.1") # Compliant __version__: str = "26.8.0.1" # Compliant +(__version__) = "26.8.0.1" # Compliant version = "26.8.0.1" # Noncompliant VERSION = "26.8.0.1" # Noncompliant __version__ = version = "26.8.0.1" # Noncompliant From 51e5557a3370e68ce4be0a3958bc214bbed60921 Mon Sep 17 00:00:00 2001 From: Erwan Le Forestier Date: Tue, 18 Aug 2026 17:04:23 +0200 Subject: [PATCH 3/5] SONARPY-4539 Reuse parenthesis normalization --- .../main/java/org/sonar/python/checks/HardcodedIPCheck.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/python-checks/src/main/java/org/sonar/python/checks/HardcodedIPCheck.java b/python-checks/src/main/java/org/sonar/python/checks/HardcodedIPCheck.java index a5ecc92d3f..a9ca1250da 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/HardcodedIPCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/HardcodedIPCheck.java @@ -132,10 +132,7 @@ private static boolean hasVersionName(AssignmentStatement assignment) { * @return whether the expression is named {@code __version__} */ private static boolean isVersionName(Expression expression) { - while (expression instanceof ParenthesizedExpression parenthesizedExpression) { - expression = parenthesizedExpression.expression(); - } - return expression instanceof Name name && "__version__".equals(name.name()); + return Expressions.removeParentheses(expression) instanceof Name name && "__version__".equals(name.name()); } private static boolean isMultilineString(StringLiteral pyStringLiteralTree) { From 99233837043ec17016bb8a56ad0091c7e41f850e Mon Sep 17 00:00:00 2001 From: Erwan Le Forestier Date: Wed, 19 Aug 2026 10:55:22 +0200 Subject: [PATCH 4/5] SONARPY-4539 Remove redundant helper documentation --- .../org/sonar/python/checks/HardcodedIPCheck.java | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/python-checks/src/main/java/org/sonar/python/checks/HardcodedIPCheck.java b/python-checks/src/main/java/org/sonar/python/checks/HardcodedIPCheck.java index a9ca1250da..066a552a97 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/HardcodedIPCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/HardcodedIPCheck.java @@ -94,11 +94,6 @@ public void initialize(Context context) { }); } - /** - * Checks whether a literal is directly assigned to the conventional version variable. - * @param stringLiteral literal to inspect - * @return whether the literal is a direct {@code __version__} value - */ private static boolean isVersionLiteral(StringLiteral stringLiteral) { Expression assignedValue = stringLiteral; while (assignedValue.parent() instanceof ParenthesizedExpression parenthesizedExpression) { @@ -113,11 +108,6 @@ private static boolean isVersionLiteral(StringLiteral stringLiteral) { && isVersionName(assignment.variable()); } - /** - * Checks whether an assignment has exactly one {@code __version__} target. - * @param assignment assignment to inspect - * @return whether the assignment has the conventional version target - */ private static boolean hasVersionName(AssignmentStatement assignment) { if (assignment.lhsExpressions().size() != 1) { return false; @@ -126,11 +116,6 @@ private static boolean hasVersionName(AssignmentStatement assignment) { return lhsExpressions.expressions().size() == 1 && isVersionName(lhsExpressions.expressions().get(0)); } - /** - * Checks whether an expression is the conventional version variable. - * @param expression expression to inspect - * @return whether the expression is named {@code __version__} - */ private static boolean isVersionName(Expression expression) { return Expressions.removeParentheses(expression) instanceof Name name && "__version__".equals(name.name()); } From fb3c657859772b9996d3a4ef6a2f307f16156642 Mon Sep 17 00:00:00 2001 From: Erwan Le Forestier Date: Wed, 19 Aug 2026 10:56:27 +0200 Subject: [PATCH 5/5] SONARPY-4539 Cover non-name version targets --- python-checks/src/test/resources/checks/hardcodedIP.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python-checks/src/test/resources/checks/hardcodedIP.py b/python-checks/src/test/resources/checks/hardcodedIP.py index 0f5f7df186..f27c91134d 100644 --- a/python-checks/src/test/resources/checks/hardcodedIP.py +++ b/python-checks/src/test/resources/checks/hardcodedIP.py @@ -29,6 +29,8 @@ version = "26.8.0.1" # Noncompliant VERSION = "26.8.0.1" # Noncompliant __version__ = version = "26.8.0.1" # Noncompliant +metadata.__version__ = "26.8.0.1" # Noncompliant +versions["__version__"] = "26.8.0.1" # Noncompliant "1080:0:0:0:8:800:200C:417A" # Noncompliant {{Make sure using this hardcoded IP address "1080:0:0:0:8:800:200C:417A" is safe here.}} "[1080::8:800:200C:417A]" # Noncompliant