Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"ruleKey": "S3577",
"hasTruePositives": true,
"falseNegatives": 46,
"falseNegatives": 47,
"falsePositives": 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"ruleKey": "S8692",
"hasTruePositives": true,
"falseNegatives": 0,
"falsePositives": 0
}
48 changes: 48 additions & 0 deletions its/ruling/src/test/resources/commons-beanutils/java-S8692.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"commons-beanutils:commons-beanutils:src/test/java/org/apache/commons/beanutils2/BeanUtilsBenchCase.java": [
186,
190,
198,
202,
219,
223,
231,
235,
252,
256,
264,
268,
285,
289,
297,
301,
318,
322,
330,
334,
352,
356,
364,
368
],
"commons-beanutils:commons-beanutils:src/test/java/org/apache/commons/beanutils2/PropertyUtilsBenchCase.java": [
175,
179,
187,
191,
208,
212,
220,
224,
241,
245,
253,
257
],
"commons-beanutils:commons-beanutils:src/test/java/org/apache/commons/beanutils2/TestResultSet.java": [
66
],
"commons-beanutils:commons-beanutils:src/test/java/org/apache/commons/beanutils2/converters/DateConverterTestBase.java": [
105
]
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume you reviewed all these sources.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, these are all occurrences of System.currentTimeMillis in tests !

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"org.eclipse.jetty:jetty-project:jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java": [
139
],
"org.eclipse.jetty:jetty-project:jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java": [
693,
744,
801,
835,
1008,
1010,
1296,
1298,
1715,
1743
]
}
36 changes: 36 additions & 0 deletions its/ruling/src/test/resources/eclipse-jetty/java-S8692.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"org.eclipse.jetty:jetty-project:jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java": [
139
],
"org.eclipse.jetty:jetty-project:jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java": [
693,
744,
801,
835,
1008,
1010,
1296,
1298,
1715,
1743
],
"org.eclipse.jetty:jetty-project:jetty-util/src/test/java/org/eclipse/jetty/util/DateCacheTest.java": [
78,
80,
83,
85,
87,
89,
91
],
"org.eclipse.jetty:jetty-project:jetty-util/src/test/java/org/eclipse/jetty/util/thread/SchedulerTest.java": [
84,
90,
106,
112,
122,
128,
149,
170
]
}
36 changes: 36 additions & 0 deletions its/ruling/src/test/resources/sonar-server/java-S8692.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/batch/ProjectDataLoaderMediumTest.java": [
620,
621
],
"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/issue/index/IssueIndexDebtTest.java": [
76
],
"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/issue/index/IssueIndexTest.java": [
107
],
"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java": [
1384,
1393,
1402
],
"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTextSearchTest.java": [
293
],
"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java": [
144,
146
],
"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/permission/index/PermissionIndexerTester.java": [
41,
48,
55
],
"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/projecttag/ws/SearchActionTest.java": [
116
],
"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/search/BaseDocTest.java": [
109,
135
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package checks.tests;

import java.time.Clock;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;

import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.when;

class SystemClockCheckSample {

@Mock
private Clock clock;

static class SecurityService {
private final Clock clock;

public SecurityService(Clock clock) {
this.clock = clock;
}

public boolean isTokenValid(Instant issuedAt) {
return issuedAt.isAfter(Instant.now(clock).minus(1, ChronoUnit.HOURS));
}
}

@Test
void testSystemClockInstants() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instant.ofEpochMilli(System.currentTimeMillis()); is compliant. Should this rule catch it too? (There is also System.nanoTime())

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think that in the first example, we should probably raise an issue on System.currentTimeMillis() (rather than Instant.ofEpochMilli(), since it doesn't have anything to do with the system clock). System.nanoTime() doesn't read the system clock and is very commonly used to measure elapsed time, so I don't think we should raise on it either.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if System.nanoTime doesn't read the wall-clock system time, it still uses the hardware to measure time (which still makes the tests non deterministic), so I think it could be targeted by the rule too

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, however my issue with including System.nanoTime is that it is often one of the recommended ways to measure elapsed time between two points in tests. It is not a problem to read System.nanoTime in a test because it does not depend on the system clock (which could change between runs or even during a run). As specified in the doc: "This method can only be used to measure elapsed time and is not related to any other notion of system or wall-clock time".

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, you're right, it seems to be out of scope for this rule

Instant now = Instant.now(); // Noncompliant {{Do not use the system clock in tests.}}
// ^^^^^^^^^^^^^
Instant now2 = Instant.now(Clock.system(ZoneId.systemDefault())); // Noncompliant {{Do not use the system clock in tests.}}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
assertTrue(now.isBefore(now2));
}

@Test
void testLocalDateTimeTypes() {
LocalDateTime dateTime1 = LocalDateTime.now(Clock.systemUTC()); // Noncompliant {{Do not use the system clock in tests.}}
// ^^^^^^^^^^^^^^^^^
LocalDateTime dateTime2 = LocalDateTime.now(); // Noncompliant {{Do not use the system clock in tests.}}
// ^^^^^^^^^^^^^^^^^^^
assertTrue(dateTime1.isBefore(dateTime2));
}

@Test
void testInjectSystemClock() {
SecurityService securityService = new SecurityService(Clock.systemDefaultZone()); // Noncompliant {{Do not use the system clock in tests.}}
// ^^^^^^^^^^^^^^^^^^^^^^^^^
assertTrue(securityService.isTokenValid(Instant.now(Clock.system(ZoneId.systemDefault())).minusSeconds(60))); // Noncompliant {{Do not use the system clock in tests.}}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}

@Test
void testSystemMethods() {
long currentTimeMillis = System.currentTimeMillis(); // Noncompliant {{Do not use the system clock in tests.}}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
long currentTimeNanos = System.nanoTime(); // Compliant: nanoTime is typically used to measure elapsed time in tests
assertTrue(currentTimeMillis < currentTimeNanos);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is an idea for another rule: if you use a suffix such as millis or nanos, don't mix them up :) It may not be straightfoward to implement though.

}

@Test
void testFixedClock() {
Instant start = Instant.now(Clock.fixed(Instant.parse("2026-05-07T10:00:00Z"), ZoneOffset.UTC)); // Compliant
Instant later = start.plus(1, ChronoUnit.MINUTES);
assertTrue(start.isBefore(later));
}

@Test
void testInjectFixedClock() {
Instant fixedPoint = Instant.parse("2026-05-07T10:00:00Z"); // Compliant
when(clock.instant()).thenReturn(fixedPoint);
when(clock.getZone()).thenReturn(ZoneOffset.UTC);

SecurityService service = new SecurityService(clock);
Instant issuedAt = Instant.parse("2026-05-07T09:30:00Z"); // Compliant
assertTrue(service.isTokenValid(issuedAt));
}

}
Comment thread
sonar-review-alpha[bot] marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* SonarQube Java
* Copyright (C) SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* You can redistribute and/or modify this program under the terms of
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
*
* 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 Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.checks.tests;

import org.sonar.check.Rule;
import org.sonar.java.checks.methods.AbstractMethodDetection;
import org.sonar.plugins.java.api.semantic.MethodMatchers;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;

@Rule(key = "S8692")
public class SystemClockCheck extends AbstractMethodDetection {

private static final MethodMatchers SYSTEM_CLOCK_MATCHERS = MethodMatchers.or(
MethodMatchers.create()
.ofTypes("java.time.LocalDate",
"java.time.LocalTime",
"java.time.LocalDateTime",
"java.time.MonthDay",
"java.time.Year",
"java.time.YearMonth",
"java.time.ZonedDateTime",
"java.time.OffsetDateTime",
"java.time.OffsetTime",
"java.time.Instant")
.names("now")
.addWithoutParametersMatcher()
.addParametersMatcher("java.time.ZoneId")
.build(),
MethodMatchers.create()
.ofTypes("java.time.Clock")
.names("systemUTC", "systemDefaultZone", "system")
.withAnyParameters()
.build(),
MethodMatchers.create()
.ofTypes("java.lang.System")
.names("currentTimeMillis")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just occured to me that new java.util.Date() is another way to read the system clock and I think it is used frequently. I know that is class should not be used, and we probably have a rule for that, but this is an orthogonal concern - someone may allow using java.util.Date in their code (compatibility with 3rd party code is a good reason to do so) and still want to have an issue about reading system clock in tests.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, I'll add calls to the Date constructors to the nodes we should raise the issue on.

.addWithoutParametersMatcher()
.build()
);

@Override
protected MethodMatchers getMethodInvocationMatchers() {
return SYSTEM_CLOCK_MATCHERS;
}

@Override
protected void onMethodInvocationFound(MethodInvocationTree mit) {
reportIssue(mit, "Do not use the system clock in tests.");
Comment thread
aurelien-coet-sonarsource marked this conversation as resolved.
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* SonarQube Java
* Copyright (C) SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* You can redistribute and/or modify this program under the terms of
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
*
* 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 Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.checks.tests;

import org.junit.jupiter.api.Test;
import org.sonar.java.checks.verifier.CheckVerifier;

import static org.sonar.java.checks.verifier.TestUtils.testCodeSourcesPath;

class SystemClockCheckTest {

@Test
void test() {
CheckVerifier.newVerifier()
.onFile(testCodeSourcesPath("checks/tests/SystemClockCheckSample.java"))
.withCheck(new SystemClockCheck())
.verifyIssues();
}

}
Loading
Loading