-
Notifications
You must be signed in to change notification settings - Fork 724
SONARJAVA-6786: S9346 - Integer values should not be cast to long for use as timestamps #6027
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,33 +1,59 @@ | ||
| <p>Using 32-bit signed integer types for timestamps can lead to serious reliability issues such as incorrect time representation, | ||
| system failures, and the Year 2038 problem.</p> | ||
| <p>This is an issue when a smaller integer type value is widened to a larger integer type and then used as an absolute timestamp, such as milliseconds | ||
| or seconds since the Unix epoch (January 1, 1970).</p> | ||
| <h2>Why is this an issue?</h2> | ||
| <p>A 32-bit signed integer can hold values from -2,147,483,648 to 2,147,483,647. While this might seem like a large range, it's insufficient for | ||
| <p>A 32-bit signed integer can hold values from -2,147,483,648 to 2,147,483,647. While this might seem like a large range, it’s insufficient for | ||
| representing timestamps:</p> | ||
| <ul> | ||
| <li><strong>Milliseconds since epoch</strong>: A 32-bit integer can only represent approximately 24.8 days of milliseconds. Any timestamp beyond | ||
| this range will overflow.</li> | ||
| <li><strong>Seconds since epoch</strong>: A 32-bit integer can represent about 68 years, covering dates from 1970 to 2038.</li> | ||
| <li><strong>Seconds since epoch</strong>: A 32-bit integer can represent about 68 years, covering dates from 1970 to 2038. This is the famous "Year | ||
| 2038 problem" for 32-bit systems.</li> | ||
| </ul> | ||
| <p>When you cast a 32-bit integer to a 64-bit integer for use as a timestamp, you're not fixing the underlying problem — the value is already | ||
| corrupted or limited by the 32-bit constraint before the cast happens.</p> | ||
| <h3>Noncompliant code example</h3> | ||
| <p>When you cast a 32-bit integer to a 64-bit integer for use as a timestamp, you’re not fixing the underlying problem — the value is already | ||
| corrupted or limited by the 32-bit constraint before the cast happens. The cast simply preserves the incorrect or overflowed value in a larger | ||
| container.</p> | ||
| <p>Timestamps should be stored as 64-bit integer values from the start to ensure they can represent dates far into the past and future without | ||
| overflow. A 64-bit integer type can represent timestamps for approximately 292 million years, which is more than sufficient for any practical | ||
| application.</p> | ||
| <p>Using a 32-bit integer for timestamps is almost always a bug that will cause:</p> | ||
| <ul> | ||
| <li>Incorrect date and time calculations</li> | ||
| <li>Application failures when processing dates outside the limited range</li> | ||
| <li>Data corruption when timestamps overflow</li> | ||
| <li>Difficult-to-diagnose issues in production systems</li> | ||
| </ul> | ||
| <p>In Java, 32-bit integers are represented by the <code>int</code> type and 64-bit integers by the <code>long</code> type.</p> | ||
| <h3>What is the potential impact?</h3> | ||
| <p>Using 32-bit signed integer types for timestamps can lead to serious reliability issues:</p> | ||
| <ul> | ||
| <li><strong>Incorrect time representation</strong>: Dates may be displayed incorrectly or calculations may produce wrong results</li> | ||
| <li><strong>System failures</strong>: Applications may crash or behave unpredictably when timestamps overflow</li> | ||
| <li><strong>Data integrity issues</strong>: Stored timestamps may be corrupted, leading to incorrect historical records</li> | ||
| <li><strong>Year 2038 problem</strong>: For second-based timestamps, the system will fail on January 19, 2038, when the maximum value representable | ||
| by a 32-bit signed integer is exceeded</li> | ||
| </ul> | ||
| <h2>How to fix it</h2> | ||
| <p>Change the variable type from <code>int</code> to <code>long</code> to properly represent the timestamp. This ensures the value can hold the full | ||
| range of possible timestamp values without overflow.</p> | ||
| <h3>Code examples</h3> | ||
| <h4>Noncompliant code example</h4> | ||
| <pre data-diff-id="1" data-diff-type="noncompliant"> | ||
| int timestamp = 1234567890; | ||
| Date date = new Date(timestamp); // Noncompliant — int implicitly widened | ||
| Date date2 = new Date((long) timestamp); // Noncompliant — cast doesn't fix overflow | ||
| long epochMillis = (long) timestamp; // Noncompliant | ||
| Date date = new Date(epochMillis); | ||
| </pre> | ||
| <h3>Compliant solution</h3> | ||
| <h4>Compliant solution</h4> | ||
| <pre data-diff-id="1" data-diff-type="compliant"> | ||
| long timestamp = 1234567890L; | ||
| Date date = new Date(timestamp); | ||
| Date date2 = new Date(timestamp); | ||
| long epochMillis = timestamp; | ||
| Date date = new Date(epochMillis); | ||
| </pre> | ||
|
Comment on lines
40
to
50
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| <h2>Resources</h2> | ||
| <h3>Documentation</h3> | ||
| <ul> | ||
| <li><a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html">Oracle Java Documentation - Primitive Data Types</a></li> | ||
| <li><a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/System.html#currentTimeMillis()">Oracle Java Documentation - | ||
| System.currentTimeMillis()</a></li> | ||
| <li><a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Instant.html">Oracle Java Documentation - Class Instant</a> | ||
| </li> | ||
| <li>Java Documentation - <a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html">Primitive Data Types</a></li> | ||
| <li>Java Documentation - <a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/System.html#currentTimeMillis()">Class | ||
| System - currentTimeMillis()</a></li> | ||
| <li>Java Documentation - <a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Instant.html">Class Instant</a></li> | ||
| </ul> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,21 +1,24 @@ | ||
| { | ||
| "title": "Integer values should not be cast to long for use as timestamps", | ||
| "type": "BUG", | ||
| "code": { | ||
| "impacts": { | ||
| "RELIABILITY": "HIGH" | ||
| }, | ||
| "attribute": "LOGICAL" | ||
| }, | ||
| "status": "ready", | ||
| "remediation": { | ||
| "func": "Constant\/Issue", | ||
| "constantCost": "5min" | ||
| "constantCost": "5 min" | ||
| }, | ||
| "tags": ["pitfall", "datetime"], | ||
| "tags": [ | ||
| "pitfall", | ||
| "datetime" | ||
| ], | ||
| "defaultSeverity": "Critical", | ||
| "ruleSpecification": "RSPEC-9346", | ||
| "sqKey": "S9346", | ||
| "scope": "All", | ||
| "quickfix": "unknown" | ||
| "scope": "Main", | ||
| "quickfix": "unknown", | ||
| "code": { | ||
| "impacts": { | ||
| "RELIABILITY": "HIGH" | ||
| }, | ||
| "attribute": "LOGICAL" | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Quality: Rule description omits the narrowing-cast pattern the check reports
The intro now scopes the rule to "a smaller integer type value is widened to a larger integer type", and "How to fix it" only says to change the variable type from
inttolong. The check also reports narrowing casts of already-correctlongvalues (new Date((int) longVar)is asserted noncompliant in the test sample), for which the documented fix (changing the declared type) does not apply — a user hitting that issue gets no applicable guidance. Mention removing the narrowing cast as a second fix case.Document the narrowing-cast case in the "How to fix it" section.:
Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎