From 9032bd6dec287c4d4e9886c2513fa4ba812f7a26 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Mon, 12 Oct 2026 13:55:41 +0200 Subject: [PATCH 1/6] SONARJAVA-7151 S1314: Ignore short octal literals and common permission patterns Co-Authored-By: Claude Opus 5.5 (1M context) --- .../sonar/java/checks/OctalValuesCheck.java | 15 +++++++++- .../test/files/checks/OctalValuesCheck.java | 28 ++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/OctalValuesCheck.java b/java-checks/src/main/java/org/sonar/java/checks/OctalValuesCheck.java index de2d8458fb0..59789951833 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/OctalValuesCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/OctalValuesCheck.java @@ -16,6 +16,7 @@ */ package org.sonar.java.checks; +import java.util.regex.Pattern; import org.sonar.check.Rule; import org.sonar.plugins.java.api.JavaFileScanner; import org.sonar.plugins.java.api.JavaFileScannerContext; @@ -26,6 +27,13 @@ @Rule(key = "S1314") public class OctalValuesCheck extends BaseTreeVisitor implements JavaFileScanner { + /** + * Matches Unix file permission modes such as {@code 0644}, {@code 0755} or {@code 04755}: + * an optional special bits digit (setuid, setgid, sticky) followed by owner, group and others digits, + * where owner has at least read permission and each digit is one of the common values 0, 4, 5, 6 or 7. + */ + private static final Pattern PERMISSION_PATTERN = Pattern.compile("0[1-7]?[4-7][04-7]{2}"); + private JavaFileScannerContext context; @Override @@ -36,7 +44,8 @@ public void scanFile(final JavaFileScannerContext context) { @Override public void visitLiteral(LiteralTree tree) { - if (tree.is(Tree.Kind.INT_LITERAL) && isOctal(tree.value())) { + String value = tree.value(); + if (tree.is(Tree.Kind.INT_LITERAL) && isOctal(value) && value.length() > 2 && !isPermission(value)) { context.reportIssue(this, tree, "Use decimal values instead of octal ones."); } } @@ -45,6 +54,10 @@ private static boolean isOctal(String value) { return value.charAt(0) == '0' && !"0".equals(value) && !isHexadecimal(value) && !isBinary(value); } + private static boolean isPermission(String value) { + return PERMISSION_PATTERN.matcher(value).matches(); + } + private static boolean isHexadecimal(String value) { return value.startsWith("0x") || value.startsWith("0X"); } diff --git a/java-checks/src/test/files/checks/OctalValuesCheck.java b/java-checks/src/test/files/checks/OctalValuesCheck.java index f96c08f5bdb..1018640f320 100644 --- a/java-checks/src/test/files/checks/OctalValuesCheck.java +++ b/java-checks/src/test/files/checks/OctalValuesCheck.java @@ -4,10 +4,36 @@ class A { int c = 42; int d = 010; // Noncompliant {{Use decimal values instead of octal ones.}} // ^^^ - int e = 00; // Noncompliant + int e = 00; int f = 0.; int g = 0x00; int h = 0X00; int j = 0b0101; int k = 0B0101; + int l = 01; + int m = 02; + int n = 07; + int o = 012; // Noncompliant + int p = 0123; // Noncompliant + int q = 0012; // Noncompliant + int r = 0311; // Noncompliant + int s = 01234; // Noncompliant + + int perm1 = 0400; + int perm2 = 0444; + int perm3 = 0600; + int perm4 = 0640; + int perm5 = 0644; + int perm6 = 0660; + int perm7 = 0664; + int perm8 = 0666; + int perm9 = 0700; + int perm10 = 0750; + int perm11 = 0755; + int perm12 = 0770; + int perm13 = 0775; + int perm14 = 0777; + int perm15 = 01777; + int perm16 = 02755; + int perm17 = 04755; } From b350f8851595bf52b5cca256a892df0aa605af38 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Tue, 13 Oct 2026 09:33:45 +0200 Subject: [PATCH 2/6] SONARJAVA-7151 S1314: Allow explicit "0_" octal prefix and mention it in the issue message Co-Authored-By: Claude Opus 5.5 (1M context) --- .../org/sonar/java/checks/OctalValuesCheck.java | 8 ++++++-- .../src/test/files/checks/OctalValuesCheck.java | 13 ++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/OctalValuesCheck.java b/java-checks/src/main/java/org/sonar/java/checks/OctalValuesCheck.java index 59789951833..d90d8b42864 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/OctalValuesCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/OctalValuesCheck.java @@ -45,8 +45,8 @@ public void scanFile(final JavaFileScannerContext context) { @Override public void visitLiteral(LiteralTree tree) { String value = tree.value(); - if (tree.is(Tree.Kind.INT_LITERAL) && isOctal(value) && value.length() > 2 && !isPermission(value)) { - context.reportIssue(this, tree, "Use decimal values instead of octal ones."); + if (tree.is(Tree.Kind.INT_LITERAL) && isOctal(value) && value.length() > 2 && !hasExplicitOctalPrefix(value) && !isPermission(value)) { + context.reportIssue(this, tree, "Use decimal values instead of octal ones. If an octal value is intended, use the more explicit \"0_\" prefix."); } } @@ -54,6 +54,10 @@ private static boolean isOctal(String value) { return value.charAt(0) == '0' && !"0".equals(value) && !isHexadecimal(value) && !isBinary(value); } + private static boolean hasExplicitOctalPrefix(String value) { + return value.startsWith("0_"); + } + private static boolean isPermission(String value) { return PERMISSION_PATTERN.matcher(value).matches(); } diff --git a/java-checks/src/test/files/checks/OctalValuesCheck.java b/java-checks/src/test/files/checks/OctalValuesCheck.java index 1018640f320..d6bc86930f8 100644 --- a/java-checks/src/test/files/checks/OctalValuesCheck.java +++ b/java-checks/src/test/files/checks/OctalValuesCheck.java @@ -2,7 +2,7 @@ class A { int a = 0; int b = 1; int c = 42; - int d = 010; // Noncompliant {{Use decimal values instead of octal ones.}} + int d = 010; // Noncompliant {{Use decimal values instead of octal ones. If an octal value is intended, use the more explicit "0_" prefix.}} // ^^^ int e = 00; int f = 0.; @@ -36,4 +36,15 @@ class A { int perm15 = 01777; int perm16 = 02755; int perm17 = 04755; + int perm18 = 0_755; + int perm19 = 0_644; + + int explicit1 = 0_7; + int explicit2 = 0_13; + int explicit3 = 0_123; + int explicit4 = 0_311; + int explicit5 = 0_1234; + int explicit6 = 0_12_34; + int underscore1 = 01_234; // Noncompliant + int underscore2 = 00_13; // Noncompliant } From c2ba39835c415a0a6cd396f12d09e8376ead310f Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Tue, 13 Oct 2026 17:02:13 +0200 Subject: [PATCH 3/6] SONARJAVA-7151 S1314: Raise on permission patterns and suggest a symbolic permission string Co-Authored-By: Claude Opus 5.5 (1M context) --- .../sonar/java/checks/OctalValuesCheck.java | 34 ++++++++++++++---- .../test/files/checks/OctalValuesCheck.java | 35 ++++++++++--------- 2 files changed, 46 insertions(+), 23 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/OctalValuesCheck.java b/java-checks/src/main/java/org/sonar/java/checks/OctalValuesCheck.java index d90d8b42864..f56af234e26 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/OctalValuesCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/OctalValuesCheck.java @@ -28,11 +28,13 @@ public class OctalValuesCheck extends BaseTreeVisitor implements JavaFileScanner { /** - * Matches Unix file permission modes such as {@code 0644}, {@code 0755} or {@code 04755}: - * an optional special bits digit (setuid, setgid, sticky) followed by owner, group and others digits, - * where owner has at least read permission and each digit is one of the common values 0, 4, 5, 6 or 7. + * Matches Unix file permission modes such as {@code 0644} or {@code 0755}: + * owner, group and others digits, where owner has at least read permission + * and each digit is one of the common values 0, 4, 5, 6 or 7. */ - private static final Pattern PERMISSION_PATTERN = Pattern.compile("0[1-7]?[4-7][04-7]{2}"); + private static final Pattern PERMISSION_PATTERN = Pattern.compile("0[4-7][04-7]{2}"); + + private static final String EXPLICIT_PREFIX_MESSAGE = "If an octal value is intended, use the more explicit \"0_\" prefix."; private JavaFileScannerContext context; @@ -45,9 +47,29 @@ public void scanFile(final JavaFileScannerContext context) { @Override public void visitLiteral(LiteralTree tree) { String value = tree.value(); - if (tree.is(Tree.Kind.INT_LITERAL) && isOctal(value) && value.length() > 2 && !hasExplicitOctalPrefix(value) && !isPermission(value)) { - context.reportIssue(this, tree, "Use decimal values instead of octal ones. If an octal value is intended, use the more explicit \"0_\" prefix."); + if (!tree.is(Tree.Kind.INT_LITERAL) || !isOctal(value) || value.length() <= 2 || hasExplicitOctalPrefix(value)) { + return; + } + if (isPermission(value)) { + context.reportIssue(this, tree, String.format("Use the symbolic permission string \"%s\" instead of this octal value. %s", toSymbolicPermissions(value), EXPLICIT_PREFIX_MESSAGE)); + } else { + context.reportIssue(this, tree, "Use decimal values instead of octal ones. " + EXPLICIT_PREFIX_MESSAGE); + } + } + + /** + * Converts an octal permission mode such as {@code 0755} into its symbolic representation such as {@code rwxr-xr-x}, + * as accepted by {@code java.nio.file.attribute.PosixFilePermissions.fromString}. + */ + private static String toSymbolicPermissions(String value) { + StringBuilder sb = new StringBuilder(); + for (char digit : value.substring(1).toCharArray()) { + int bits = digit - '0'; + sb.append((bits & 4) != 0 ? 'r' : '-') + .append((bits & 2) != 0 ? 'w' : '-') + .append((bits & 1) != 0 ? 'x' : '-'); } + return sb.toString(); } private static boolean isOctal(String value) { diff --git a/java-checks/src/test/files/checks/OctalValuesCheck.java b/java-checks/src/test/files/checks/OctalValuesCheck.java index d6bc86930f8..818865a998b 100644 --- a/java-checks/src/test/files/checks/OctalValuesCheck.java +++ b/java-checks/src/test/files/checks/OctalValuesCheck.java @@ -19,23 +19,24 @@ class A { int r = 0311; // Noncompliant int s = 01234; // Noncompliant - int perm1 = 0400; - int perm2 = 0444; - int perm3 = 0600; - int perm4 = 0640; - int perm5 = 0644; - int perm6 = 0660; - int perm7 = 0664; - int perm8 = 0666; - int perm9 = 0700; - int perm10 = 0750; - int perm11 = 0755; - int perm12 = 0770; - int perm13 = 0775; - int perm14 = 0777; - int perm15 = 01777; - int perm16 = 02755; - int perm17 = 04755; + int perm1 = 0400; // Noncompliant {{Use the symbolic permission string "r--------" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} +// ^^^^ + int perm2 = 0444; // Noncompliant {{Use the symbolic permission string "r--r--r--" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm3 = 0600; // Noncompliant {{Use the symbolic permission string "rw-------" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm4 = 0640; // Noncompliant {{Use the symbolic permission string "rw-r-----" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm5 = 0644; // Noncompliant {{Use the symbolic permission string "rw-r--r--" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm6 = 0660; // Noncompliant {{Use the symbolic permission string "rw-rw----" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm7 = 0664; // Noncompliant {{Use the symbolic permission string "rw-rw-r--" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm8 = 0666; // Noncompliant {{Use the symbolic permission string "rw-rw-rw-" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm9 = 0700; // Noncompliant {{Use the symbolic permission string "rwx------" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm10 = 0750; // Noncompliant {{Use the symbolic permission string "rwxr-x---" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm11 = 0755; // Noncompliant {{Use the symbolic permission string "rwxr-xr-x" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm12 = 0770; // Noncompliant {{Use the symbolic permission string "rwxrwx---" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm13 = 0775; // Noncompliant {{Use the symbolic permission string "rwxrwxr-x" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm14 = 0777; // Noncompliant {{Use the symbolic permission string "rwxrwxrwx" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm15 = 01777; // Noncompliant {{Use decimal values instead of octal ones. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm16 = 02755; // Noncompliant {{Use decimal values instead of octal ones. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm17 = 04755; // Noncompliant {{Use decimal values instead of octal ones. If an octal value is intended, use the more explicit "0_" prefix.}} int perm18 = 0_755; int perm19 = 0_644; From 95e8234ac2d96a8d4fda1ac62f84e397f602ac7b Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Wed, 14 Oct 2026 10:10:58 +0200 Subject: [PATCH 4/6] SONARJAVA-7151 S1314: Mention the decimal alternative in the permission-mode message Co-Authored-By: Claude Opus 5.5 (1M context) --- .../sonar/java/checks/OctalValuesCheck.java | 2 +- .../test/files/checks/OctalValuesCheck.java | 28 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/OctalValuesCheck.java b/java-checks/src/main/java/org/sonar/java/checks/OctalValuesCheck.java index f56af234e26..e71adb1d33e 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/OctalValuesCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/OctalValuesCheck.java @@ -51,7 +51,7 @@ public void visitLiteral(LiteralTree tree) { return; } if (isPermission(value)) { - context.reportIssue(this, tree, String.format("Use the symbolic permission string \"%s\" instead of this octal value. %s", toSymbolicPermissions(value), EXPLICIT_PREFIX_MESSAGE)); + context.reportIssue(this, tree, String.format("If this is a file permission mode, use the symbolic permission string \"%s\"; otherwise use the decimal value. %s", toSymbolicPermissions(value), EXPLICIT_PREFIX_MESSAGE)); } else { context.reportIssue(this, tree, "Use decimal values instead of octal ones. " + EXPLICIT_PREFIX_MESSAGE); } diff --git a/java-checks/src/test/files/checks/OctalValuesCheck.java b/java-checks/src/test/files/checks/OctalValuesCheck.java index 818865a998b..a24a411323b 100644 --- a/java-checks/src/test/files/checks/OctalValuesCheck.java +++ b/java-checks/src/test/files/checks/OctalValuesCheck.java @@ -19,21 +19,21 @@ class A { int r = 0311; // Noncompliant int s = 01234; // Noncompliant - int perm1 = 0400; // Noncompliant {{Use the symbolic permission string "r--------" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm1 = 0400; // Noncompliant {{If this is a file permission mode, use the symbolic permission string "r--------"; otherwise use the decimal value. If an octal value is intended, use the more explicit "0_" prefix.}} // ^^^^ - int perm2 = 0444; // Noncompliant {{Use the symbolic permission string "r--r--r--" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} - int perm3 = 0600; // Noncompliant {{Use the symbolic permission string "rw-------" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} - int perm4 = 0640; // Noncompliant {{Use the symbolic permission string "rw-r-----" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} - int perm5 = 0644; // Noncompliant {{Use the symbolic permission string "rw-r--r--" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} - int perm6 = 0660; // Noncompliant {{Use the symbolic permission string "rw-rw----" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} - int perm7 = 0664; // Noncompliant {{Use the symbolic permission string "rw-rw-r--" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} - int perm8 = 0666; // Noncompliant {{Use the symbolic permission string "rw-rw-rw-" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} - int perm9 = 0700; // Noncompliant {{Use the symbolic permission string "rwx------" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} - int perm10 = 0750; // Noncompliant {{Use the symbolic permission string "rwxr-x---" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} - int perm11 = 0755; // Noncompliant {{Use the symbolic permission string "rwxr-xr-x" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} - int perm12 = 0770; // Noncompliant {{Use the symbolic permission string "rwxrwx---" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} - int perm13 = 0775; // Noncompliant {{Use the symbolic permission string "rwxrwxr-x" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} - int perm14 = 0777; // Noncompliant {{Use the symbolic permission string "rwxrwxrwx" instead of this octal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm2 = 0444; // Noncompliant {{If this is a file permission mode, use the symbolic permission string "r--r--r--"; otherwise use the decimal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm3 = 0600; // Noncompliant {{If this is a file permission mode, use the symbolic permission string "rw-------"; otherwise use the decimal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm4 = 0640; // Noncompliant {{If this is a file permission mode, use the symbolic permission string "rw-r-----"; otherwise use the decimal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm5 = 0644; // Noncompliant {{If this is a file permission mode, use the symbolic permission string "rw-r--r--"; otherwise use the decimal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm6 = 0660; // Noncompliant {{If this is a file permission mode, use the symbolic permission string "rw-rw----"; otherwise use the decimal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm7 = 0664; // Noncompliant {{If this is a file permission mode, use the symbolic permission string "rw-rw-r--"; otherwise use the decimal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm8 = 0666; // Noncompliant {{If this is a file permission mode, use the symbolic permission string "rw-rw-rw-"; otherwise use the decimal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm9 = 0700; // Noncompliant {{If this is a file permission mode, use the symbolic permission string "rwx------"; otherwise use the decimal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm10 = 0750; // Noncompliant {{If this is a file permission mode, use the symbolic permission string "rwxr-x---"; otherwise use the decimal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm11 = 0755; // Noncompliant {{If this is a file permission mode, use the symbolic permission string "rwxr-xr-x"; otherwise use the decimal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm12 = 0770; // Noncompliant {{If this is a file permission mode, use the symbolic permission string "rwxrwx---"; otherwise use the decimal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm13 = 0775; // Noncompliant {{If this is a file permission mode, use the symbolic permission string "rwxrwxr-x"; otherwise use the decimal value. If an octal value is intended, use the more explicit "0_" prefix.}} + int perm14 = 0777; // Noncompliant {{If this is a file permission mode, use the symbolic permission string "rwxrwxrwx"; otherwise use the decimal value. If an octal value is intended, use the more explicit "0_" prefix.}} int perm15 = 01777; // Noncompliant {{Use decimal values instead of octal ones. If an octal value is intended, use the more explicit "0_" prefix.}} int perm16 = 02755; // Noncompliant {{Use decimal values instead of octal ones. If an octal value is intended, use the more explicit "0_" prefix.}} int perm17 = 04755; // Noncompliant {{Use decimal values instead of octal ones. If an octal value is intended, use the more explicit "0_" prefix.}} From 681f98616e1aa1634aac4ff2933f4d959737a718 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Wed, 14 Oct 2026 10:13:20 +0200 Subject: [PATCH 5/6] SONARJAVA-7151 S1314: Split long line in OctalValuesCheck Co-Authored-By: Claude Opus 5.5 (1M context) --- .../main/java/org/sonar/java/checks/OctalValuesCheck.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/OctalValuesCheck.java b/java-checks/src/main/java/org/sonar/java/checks/OctalValuesCheck.java index e71adb1d33e..c1fe4846f10 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/OctalValuesCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/OctalValuesCheck.java @@ -51,7 +51,11 @@ public void visitLiteral(LiteralTree tree) { return; } if (isPermission(value)) { - context.reportIssue(this, tree, String.format("If this is a file permission mode, use the symbolic permission string \"%s\"; otherwise use the decimal value. %s", toSymbolicPermissions(value), EXPLICIT_PREFIX_MESSAGE)); + String message = String.format( + "If this is a file permission mode, use the symbolic permission string \"%s\"; otherwise use the decimal value. %s", + toSymbolicPermissions(value), + EXPLICIT_PREFIX_MESSAGE); + context.reportIssue(this, tree, message); } else { context.reportIssue(this, tree, "Use decimal values instead of octal ones. " + EXPLICIT_PREFIX_MESSAGE); } From 012697b741a2a6a5663e7dd7808d74b67fb40203 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Fri, 16 Oct 2026 14:40:35 +0200 Subject: [PATCH 6/6] SONARJAVA-7151 S1314: Update rule metadata and rename test sample Co-Authored-By: Claude Opus 5.5 (1M context) --- ...Check.java => OctalValuesCheckSample.java} | 0 .../java/checks/OctalValuesCheckTest.java | 4 +- .../org/sonar/l10n/java/rules/java/S1314.html | 77 +++++++++++++++++-- .../main/resources/profiles/Sonar_way/S1314 | 0 4 files changed, 73 insertions(+), 8 deletions(-) rename java-checks/src/test/files/checks/{OctalValuesCheck.java => OctalValuesCheckSample.java} (100%) create mode 100644 sonar-java-plugin/src/main/resources/profiles/Sonar_way/S1314 diff --git a/java-checks/src/test/files/checks/OctalValuesCheck.java b/java-checks/src/test/files/checks/OctalValuesCheckSample.java similarity index 100% rename from java-checks/src/test/files/checks/OctalValuesCheck.java rename to java-checks/src/test/files/checks/OctalValuesCheckSample.java diff --git a/java-checks/src/test/java/org/sonar/java/checks/OctalValuesCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/OctalValuesCheckTest.java index 0e07be63abf..9aa16168091 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/OctalValuesCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/OctalValuesCheckTest.java @@ -24,7 +24,7 @@ class OctalValuesCheckTest { @Test void test() { CheckVerifier.newVerifier() - .onFile("src/test/files/checks/OctalValuesCheck.java") + .onFile("src/test/files/checks/OctalValuesCheckSample.java") .withCheck(new OctalValuesCheck()) .verifyIssues(); } @@ -32,7 +32,7 @@ void test() { @Test void test_without_semantic() { CheckVerifier.newVerifier() - .onFile("src/test/files/checks/OctalValuesCheck.java") + .onFile("src/test/files/checks/OctalValuesCheckSample.java") .withCheck(new OctalValuesCheck()) .withoutSemantic() .verifyIssues(); diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S1314.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S1314.html index adf30b1fdf0..d306ada71f2 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S1314.html +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S1314.html @@ -1,15 +1,80 @@ +

This rule raises an issue when an integer literal starts with a zero followed by other digits, such as 010 or 060. Java +interprets such a literal as a base-8 octal value rather than a base-10 decimal value.

Why is this an issue?

-

Integer literals starting with a zero are octal rather than decimal values. While using octal values is fully supported, most developers do not -have experience with them. They may not recognize octal values as such, mistaking them instead for decimal values.

-

Noncompliant code example

+

In Java, integer literals that begin with a leading zero are interpreted as octal numbers, not decimal numbers. This is a legacy feature inherited +from C.

+

For example:

+
    +
  • 010 represents 8 in decimal, computed as 1×8 + 0
  • +
  • 060 represents 48 in decimal, computed as 6×8 + 0
  • +
  • 01000 represents 512 in decimal, computed as 1×512 + 0×64 + 0×8 + 0
  • +
+

This syntax is counterintuitive: most developers expect a literal such as 060 to mean sixty, with the leading zero being mere padding. +There is no visual cue that octal interpretation is happening, and code reviewers can easily miss the leading zero.

+

Octal literals are rarely needed in modern code. When a value is better expressed in another base, Java provides explicit prefixes: 0x +for hexadecimal and 0b for binary.

+

A common legitimate use of octal literals is Unix file permission modes, such as 0644 or 0755. Even there, the symbolic +notation accepted by java.nio.file.attribute.PosixFilePermissions.fromString, such as "rw-r—​r--" or +"rwxr-xr-x", is easier to read and harder to get wrong.

+

For an integer literal whose complete source text matches 0[4-7][04-7]{2}, the rule message suggests the equivalent symbolic +permission string. This means exactly four characters: a leading zero, an owner digit from 4 to 7, and group and others digits each equal to 0, 4, 5, +6 or 7. Literals such as 04755, 0311 or 06_44 do not match this criterion and receive the general octal message +instead. Permission-mode literals are still reported, not exempted. The dedicated message also mentions the decimal value as an alternative, since +such a literal may be a zero-padded decimal like a time of day 0700.

+

Exceptions

+

This rule does not raise an issue for:

+
    +
  • Literals with at most two characters, such as 00 or 07. Their octal and decimal values are identical, so there is no + risk of confusion.
  • +
  • Octal literals written with the 0_ prefix, such as 0_7, 0_13 or 0_755, which makes the + intent explicit.
  • +
-int myNumber = 010; // Noncompliant. myNumber will hold 8, not 10 - was this really expected?
+int zero = 00;            // Compliant: same value in octal and decimal
+int seven = 07;           // Compliant: same value in octal and decimal
+int explicitOctal = 0_13; // Compliant: explicit octal intent
+int mode = 0_755;         // Compliant: explicit octal intent
+
+

What is the potential impact?

+

Using an octal literal when a decimal value is intended leads to incorrect calculations and logic errors. For example, setting a timeout to +060 seconds results in a 48-second timeout instead of the expected 60 seconds.

+

Such bugs are difficult to diagnose because the code compiles without any warning, and the discrepancy between the expected and actual values may +only show up under specific conditions.

+

How to fix it

+

Use the decimal value instead. For file permission modes, prefer the symbolic permission string. If an octal value is really intended, use the +0_ prefix, which makes it clear to readers that the literal is octal.

+

Code examples

+

Noncompliant code example

+
+int myNumber = 010;    // Noncompliant: myNumber holds 8, not 10
+int timeout = 060;     // Noncompliant: timeout holds 48, not 60
+int count = 01000;     // Noncompliant: count holds 512, not 1000
 
-

Compliant solution

+

Compliant solution

+
+int myNumber = 10;
+int timeout = 60;
+int count = 1000;
+
+

Noncompliant code example

+
+int mode = 0640; // Noncompliant: if this is a permission mode, use "rw-r-----" instead
+
+

Compliant solution

+
+Set<PosixFilePermission> mode = PosixFilePermissions.fromString("rw-r-----");
+
+

Or, if an octal value is intended, such as a file permission mode passed to a native API:

-int myNumber = 8;
+int mode = 0_640;
 

Resources

+

Documentation

+ +

Standards

  • CERT, DCL18-C. - Do not begin integer constants with 0 when specifying a decimal value
  • diff --git a/sonar-java-plugin/src/main/resources/profiles/Sonar_way/S1314 b/sonar-java-plugin/src/main/resources/profiles/Sonar_way/S1314 new file mode 100644 index 00000000000..e69de29bb2d