Skip to content

Commit f360508

Browse files
committed
java.lang.ArithmeticException: long overflow
java.lang.Math.addExact(Math.java:932) at org.apache.commons.io.file.attribute.FileTimes.ntfsTimeToFileTime(FileTimes.java:164). See also https://issues.apache.org/jira/browse/MDEP-978
1 parent 2ca87c1 commit f360508

File tree

3 files changed

+86
-40
lines changed

3 files changed

+86
-40
lines changed

src/changes/changes.xml

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ The <action> type attribute can be add,update,fix,remove.
6767
<action dev="ggregory" type="fix" due-to="Gary Gregory">Improve performance of IOUtils.contentEquals(InputStream, InputStream) by about 13%.</action>
6868
<action dev="ggregory" type="fix" issue="IO-870" due-to="Gary Gregory">PathUtils.copyFileToDirectory() across file systems #728.</action>
6969
<action dev="ggregory" type="fix" issue="IO-871" due-to="Éamonn McManus, Gary Gregory">IOUtils.contentEquals is incorrect when InputStream.available under-reports.</action>
70+
<action dev="ggregory" type="fix" due-to="Gary Gregory">java.lang.ArithmeticException: long overflow java.lang.Math.addExact(Math.java:932) at org.apache.commons.io.file.attribute.FileTimes.ntfsTimeToFileTime(FileTimes.java:164). See also https://issues.apache.org/jira/browse/MDEP-978.</action>
7071
<!-- ADD -->
7172
<action dev="ggregory" type="add" issue="IO-860" due-to="Nico Strecker, Gary Gregory">Add ThrottledInputStream.Builder.setMaxBytes(long, ChronoUnit).</action>
7273
<action dev="ggregory" type="add" due-to="Gary Gregory">Add IOIterable.</action>

src/main/java/org/apache/commons/io/file/attribute/FileTimes.java

+45-24
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
package org.apache.commons.io.file.attribute;
1919

2020
import java.io.IOException;
21+
import java.math.BigDecimal;
22+
import java.math.MathContext;
23+
import java.math.RoundingMode;
2124
import java.nio.file.Files;
2225
import java.nio.file.Path;
2326
import java.nio.file.attribute.FileTime;
@@ -32,6 +35,8 @@
3235
*/
3336
public final class FileTimes {
3437

38+
private static final MathContext MATH_CONTEXT = new MathContext(0, RoundingMode.FLOOR);
39+
3540
/**
3641
* Constant for the {@code 1970-01-01T00:00:00Z} {@link Instant#EPOCH epoch} as a time stamp attribute.
3742
*
@@ -44,23 +49,30 @@ public final class FileTimes {
4449
*
4550
* <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms724290%28v=vs.85%29.aspx">Windows File Times</a>
4651
* <p>
47-
* A file time is a 64-bit value that represents the number of 100-nanosecond intervals that have elapsed since 12:00
48-
* A.M. January 1, 1601 Coordinated Universal Time (UTC). This is the offset of Windows time 0 to Unix epoch in
49-
* 100-nanosecond intervals.
52+
* A file time is a 64-bit value that represents the number of 100-nanosecond intervals that have elapsed since 12:00 A.M. January 1, 1601 Coordinated
53+
* Universal Time (UTC). This is the offset of Windows time 0 to Unix epoch in 100-nanosecond intervals.
5054
* </p>
5155
*/
52-
static final long WINDOWS_EPOCH_OFFSET = -116444736000000000L;
56+
static final long UNIX_TO_NTFS_OFFSET = -116444736000000000L;
57+
58+
private static final BigDecimal UNIX_TO_NTFS_OFFSET_BD = BigDecimal.valueOf(UNIX_TO_NTFS_OFFSET);
5359

5460
/**
5561
* The amount of 100-nanosecond intervals in one second.
5662
*/
5763
private static final long HUNDRED_NANOS_PER_SECOND = TimeUnit.SECONDS.toNanos(1) / 100;
5864

65+
private static final BigDecimal HUNDRED_NANOS_PER_SECOND_BD = BigDecimal.valueOf(HUNDRED_NANOS_PER_SECOND);
66+
5967
/**
6068
* The amount of 100-nanosecond intervals in one millisecond.
6169
*/
6270
static final long HUNDRED_NANOS_PER_MILLISECOND = TimeUnit.MILLISECONDS.toNanos(1) / 100;
6371

72+
private static final long HUNDRED = 100L;
73+
74+
private static final BigDecimal HUNDRED_BD = BigDecimal.valueOf(HUNDRED);
75+
6476
/**
6577
* Converts standard Unix time (in seconds, UTC/GMT) to {@link FileTime}.
6678
*
@@ -100,7 +112,7 @@ public static boolean isUnixTime(final long seconds) {
100112
/**
101113
* Subtracts milliseconds from a source FileTime.
102114
*
103-
* @param fileTime The source FileTime.
115+
* @param fileTime The source FileTime.
104116
* @param millisToSubtract The milliseconds to subtract.
105117
* @return The resulting FileTime.
106118
*/
@@ -111,7 +123,7 @@ public static FileTime minusMillis(final FileTime fileTime, final long millisToS
111123
/**
112124
* Subtracts nanoseconds from a source FileTime.
113125
*
114-
* @param fileTime The source FileTime.
126+
* @param fileTime The source FileTime.
115127
* @param nanosToSubtract The nanoseconds to subtract.
116128
* @return The resulting FileTime.
117129
*/
@@ -122,7 +134,7 @@ public static FileTime minusNanos(final FileTime fileTime, final long nanosToSub
122134
/**
123135
* Subtracts seconds from a source FileTime.
124136
*
125-
* @param fileTime The source FileTime.
137+
* @param fileTime The source FileTime.
126138
* @param secondsToSubtract The seconds to subtract.
127139
* @return The resulting FileTime.
128140
*/
@@ -150,7 +162,7 @@ public static FileTime now() {
150162
* @see <a href="https://learn.microsoft.com/en-us/windows/win32/sysinfo/file-times">NTFS File Times</a>
151163
*/
152164
public static Date ntfsTimeToDate(final long ntfsTime) {
153-
final long javaHundredNanos = Math.addExact(ntfsTime, WINDOWS_EPOCH_OFFSET);
165+
final long javaHundredNanos = Math.addExact(ntfsTime, UNIX_TO_NTFS_OFFSET);
154166
final long javaMillis = Math.floorDiv(javaHundredNanos, HUNDRED_NANOS_PER_MILLISECOND);
155167
return new Date(javaMillis);
156168
}
@@ -167,16 +179,23 @@ public static Date ntfsTimeToDate(final long ntfsTime) {
167179
* @see <a href="https://learn.microsoft.com/en-us/windows/win32/sysinfo/file-times">NTFS File Times</a>
168180
*/
169181
public static FileTime ntfsTimeToFileTime(final long ntfsTime) {
170-
final long javaHundredsNanos = Math.addExact(ntfsTime, WINDOWS_EPOCH_OFFSET);
171-
final long javaSeconds = Math.floorDiv(javaHundredsNanos, HUNDRED_NANOS_PER_SECOND);
172-
final long javaNanos = Math.floorMod(javaHundredsNanos, HUNDRED_NANOS_PER_SECOND) * 100;
173-
return FileTime.from(Instant.ofEpochSecond(javaSeconds, javaNanos));
182+
return FileTime.from(ntfsTimeToInstant(ntfsTime));
183+
}
184+
185+
static Instant ntfsTimeToInstant(final BigDecimal ntfsTime) {
186+
final BigDecimal javaHundredsNanos = ntfsTime.add(UNIX_TO_NTFS_OFFSET_BD);
187+
final BigDecimal[] dar = javaHundredsNanos.divideAndRemainder(HUNDRED_NANOS_PER_SECOND_BD, MATH_CONTEXT);
188+
return Instant.ofEpochSecond(dar[0].longValueExact(), dar[1].multiply(HUNDRED_BD).longValueExact());
189+
}
190+
191+
static Instant ntfsTimeToInstant(final long ntfsTime) {
192+
return ntfsTimeToInstant(BigDecimal.valueOf(ntfsTime));
174193
}
175194

176195
/**
177196
* Adds milliseconds to a source FileTime.
178197
*
179-
* @param fileTime The source FileTime.
198+
* @param fileTime The source FileTime.
180199
* @param millisToAdd The milliseconds to add.
181200
* @return The resulting FileTime.
182201
*/
@@ -187,7 +206,7 @@ public static FileTime plusMillis(final FileTime fileTime, final long millisToAd
187206
/**
188207
* Adds nanoseconds from a source FileTime.
189208
*
190-
* @param fileTime The source FileTime.
209+
* @param fileTime The source FileTime.
191210
* @param nanosToSubtract The nanoseconds to subtract.
192211
* @return The resulting FileTime.
193212
*/
@@ -198,7 +217,7 @@ public static FileTime plusNanos(final FileTime fileTime, final long nanosToSubt
198217
/**
199218
* Adds seconds to a source FileTime.
200219
*
201-
* @param fileTime The source FileTime.
220+
* @param fileTime The source FileTime.
202221
* @param secondsToAdd The seconds to add.
203222
* @return The resulting FileTime.
204223
*/
@@ -217,8 +236,7 @@ public static void setLastModifiedTime(final Path path) throws IOException {
217236
}
218237

219238
/**
220-
* Converts {@link FileTime} to a {@link Date}. If the provided FileTime is {@code null}, the returned Date is also
221-
* {@code null}.
239+
* Converts {@link FileTime} to a {@link Date}. If the provided FileTime is {@code null}, the returned Date is also {@code null}.
222240
*
223241
* @param fileTime the file time to be converted.
224242
* @return a {@link Date} which corresponds to the supplied time, or {@code null} if the time is {@code null}.
@@ -229,8 +247,7 @@ public static Date toDate(final FileTime fileTime) {
229247
}
230248

231249
/**
232-
* Converts {@link Date} to a {@link FileTime}. If the provided Date is {@code null}, the returned FileTime is also
233-
* {@code null}.
250+
* Converts {@link Date} to a {@link FileTime}. If the provided Date is {@code null}, the returned FileTime is also {@code null}.
234251
*
235252
* @param date the date to be converted.
236253
* @return a {@link FileTime} which corresponds to the supplied date, or {@code null} if the date is {@code null}.
@@ -251,7 +268,7 @@ public static FileTime toFileTime(final Date date) {
251268
*/
252269
public static long toNtfsTime(final Date date) {
253270
final long javaHundredNanos = date.getTime() * HUNDRED_NANOS_PER_MILLISECOND;
254-
return Math.subtractExact(javaHundredNanos, WINDOWS_EPOCH_OFFSET);
271+
return Math.subtractExact(javaHundredNanos, UNIX_TO_NTFS_OFFSET);
255272
}
256273

257274
/**
@@ -264,9 +281,13 @@ public static long toNtfsTime(final Date date) {
264281
* @return the NTFS time, 100-nanosecond units since 1 January 1601.
265282
*/
266283
public static long toNtfsTime(final FileTime fileTime) {
267-
final Instant instant = fileTime.toInstant();
268-
final long javaHundredNanos = instant.getEpochSecond() * HUNDRED_NANOS_PER_SECOND + instant.getNano() / 100;
269-
return Math.subtractExact(javaHundredNanos, WINDOWS_EPOCH_OFFSET);
284+
return toNtfsTime(fileTime.toInstant());
285+
}
286+
287+
static long toNtfsTime(final Instant instant) {
288+
final BigDecimal javaHundredNanos = BigDecimal.valueOf(instant.getEpochSecond()).multiply(HUNDRED_NANOS_PER_SECOND_BD)
289+
.add(BigDecimal.valueOf(instant.getNano() / 100));
290+
return javaHundredNanos.subtract(UNIX_TO_NTFS_OFFSET_BD).longValueExact();
270291
}
271292

272293
/**
@@ -281,7 +302,7 @@ public static long toNtfsTime(final FileTime fileTime) {
281302
*/
282303
public static long toNtfsTime(final long javaTime) {
283304
final long javaHundredNanos = javaTime * HUNDRED_NANOS_PER_MILLISECOND;
284-
return Math.subtractExact(javaHundredNanos, WINDOWS_EPOCH_OFFSET);
305+
return Math.subtractExact(javaHundredNanos, UNIX_TO_NTFS_OFFSET);
285306
}
286307

287308
/**

src/test/java/org/apache/commons/io/file/attribute/FileTimesTest.java

+40-16
Original file line numberDiff line numberDiff line change
@@ -49,31 +49,42 @@ public static Stream<Arguments> dateToNtfsProvider() {
4949
Arguments.of("1600-12-31T23:59:59.999Z", -FileTimes.HUNDRED_NANOS_PER_MILLISECOND),
5050
Arguments.of("1600-12-31T23:59:59.999Z", -FileTimes.HUNDRED_NANOS_PER_MILLISECOND + 1),
5151
Arguments.of("1600-12-31T23:59:59.998Z", -FileTimes.HUNDRED_NANOS_PER_MILLISECOND - 1),
52-
Arguments.of("1970-01-01T00:00:00.000Z", -FileTimes.WINDOWS_EPOCH_OFFSET),
53-
Arguments.of("1970-01-01T00:00:00.000Z", -FileTimes.WINDOWS_EPOCH_OFFSET + 1),
54-
Arguments.of("1970-01-01T00:00:00.001Z", -FileTimes.WINDOWS_EPOCH_OFFSET + FileTimes.HUNDRED_NANOS_PER_MILLISECOND),
55-
Arguments.of("1969-12-31T23:59:59.999Z", -FileTimes.WINDOWS_EPOCH_OFFSET - 1),
56-
Arguments.of("1969-12-31T23:59:59.999Z", -FileTimes.WINDOWS_EPOCH_OFFSET - FileTimes.HUNDRED_NANOS_PER_MILLISECOND));
52+
Arguments.of("1970-01-01T00:00:00.000Z", -FileTimes.UNIX_TO_NTFS_OFFSET),
53+
Arguments.of("1970-01-01T00:00:00.000Z", -FileTimes.UNIX_TO_NTFS_OFFSET + 1),
54+
Arguments.of("1970-01-01T00:00:00.001Z", -FileTimes.UNIX_TO_NTFS_OFFSET + FileTimes.HUNDRED_NANOS_PER_MILLISECOND),
55+
Arguments.of("1969-12-31T23:59:59.999Z", -FileTimes.UNIX_TO_NTFS_OFFSET - 1),
56+
Arguments.of("1969-12-31T23:59:59.999Z", -FileTimes.UNIX_TO_NTFS_OFFSET - FileTimes.HUNDRED_NANOS_PER_MILLISECOND));
5757
// @formatter:on
5858
}
5959

60-
public static Stream<Arguments> fileTimeToNtfsProvider() {
60+
public static Stream<Arguments> fileTimeNanoUnitsToNtfsProvider() {
6161
// @formatter:off
6262
return Stream.of(
6363
Arguments.of("1601-01-01T00:00:00.0000000Z", 0),
6464
Arguments.of("1601-01-01T00:00:00.0000001Z", 1),
6565
Arguments.of("1600-12-31T23:59:59.9999999Z", -1),
66+
Arguments.of("+30828-09-14T02:48:05.477580700Z", Long.MAX_VALUE),
67+
Arguments.of("-27627-04-19T21:11:54.522419200Z", Long.MIN_VALUE),
6668
Arguments.of("1601-01-01T00:00:00.0010000Z", FileTimes.HUNDRED_NANOS_PER_MILLISECOND),
6769
Arguments.of("1601-01-01T00:00:00.0010001Z", FileTimes.HUNDRED_NANOS_PER_MILLISECOND + 1),
6870
Arguments.of("1601-01-01T00:00:00.0009999Z", FileTimes.HUNDRED_NANOS_PER_MILLISECOND - 1),
6971
Arguments.of("1600-12-31T23:59:59.9990000Z", -FileTimes.HUNDRED_NANOS_PER_MILLISECOND),
7072
Arguments.of("1600-12-31T23:59:59.9990001Z", -FileTimes.HUNDRED_NANOS_PER_MILLISECOND + 1),
7173
Arguments.of("1600-12-31T23:59:59.9989999Z", -FileTimes.HUNDRED_NANOS_PER_MILLISECOND - 1),
72-
Arguments.of("1970-01-01T00:00:00.0000000Z", -FileTimes.WINDOWS_EPOCH_OFFSET),
73-
Arguments.of("1970-01-01T00:00:00.0000001Z", -FileTimes.WINDOWS_EPOCH_OFFSET + 1),
74-
Arguments.of("1970-01-01T00:00:00.0010000Z", -FileTimes.WINDOWS_EPOCH_OFFSET + FileTimes.HUNDRED_NANOS_PER_MILLISECOND),
75-
Arguments.of("1969-12-31T23:59:59.9999999Z", -FileTimes.WINDOWS_EPOCH_OFFSET - 1),
76-
Arguments.of("1969-12-31T23:59:59.9990000Z", -FileTimes.WINDOWS_EPOCH_OFFSET - FileTimes.HUNDRED_NANOS_PER_MILLISECOND));
74+
Arguments.of("1970-01-01T00:00:00.0000000Z", -FileTimes.UNIX_TO_NTFS_OFFSET),
75+
Arguments.of("1970-01-01T00:00:00.0000001Z", -FileTimes.UNIX_TO_NTFS_OFFSET + 1),
76+
Arguments.of("1970-01-01T00:00:00.0010000Z", -FileTimes.UNIX_TO_NTFS_OFFSET + FileTimes.HUNDRED_NANOS_PER_MILLISECOND),
77+
Arguments.of("1969-12-31T23:59:59.9999999Z", -FileTimes.UNIX_TO_NTFS_OFFSET - 1),
78+
Arguments.of("1969-12-31T23:59:59.9990000Z", -FileTimes.UNIX_TO_NTFS_OFFSET - FileTimes.HUNDRED_NANOS_PER_MILLISECOND));
79+
// @formatter:on
80+
}
81+
82+
public static Stream<Arguments> fileTimeToNtfsProvider() {
83+
// @formatter:off
84+
return Stream.of(
85+
Arguments.of("1970-01-01T00:00:00Z", FileTime.from(Instant.EPOCH)),
86+
Arguments.of("1969-12-31T23:59:00Z", FileTime.from(Instant.EPOCH.minusSeconds(60))),
87+
Arguments.of("1970-01-01T00:01:00Z", FileTime.from(Instant.EPOCH.plusSeconds(60))));
7788
// @formatter:on
7889
}
7990

@@ -113,16 +124,26 @@ public void testEpoch() {
113124
}
114125

115126
@ParameterizedTest
116-
@MethodSource("fileTimeToNtfsProvider")
127+
@MethodSource("fileTimeNanoUnitsToNtfsProvider")
117128
public void testFileTimeToDate(final String instant, final long ignored) {
118129
final Instant parsedInstant = Instant.parse(instant);
119130
final FileTime parsedFileTime = FileTime.from(parsedInstant);
120131
final Date parsedDate = Date.from(parsedInstant);
121132
assertEquals(parsedDate, FileTimes.toDate(parsedFileTime));
122133
}
123134

135+
//@Disabled
124136
@ParameterizedTest
125137
@MethodSource("fileTimeToNtfsProvider")
138+
public void testFileTimeToNtfsTime(final String instantStr, final FileTime fileTime) {
139+
final Instant instant = Instant.parse(instantStr);
140+
final FileTime parsed = FileTime.from(instant);
141+
assertEquals(instant, parsed.toInstant());
142+
assertEquals(fileTime, FileTimes.ntfsTimeToFileTime(FileTimes.toNtfsTime(parsed)));
143+
}
144+
145+
@ParameterizedTest
146+
@MethodSource("fileTimeNanoUnitsToNtfsProvider")
126147
public void testFileTimeToNtfsTime(final String instant, final long ntfsTime) {
127148
final FileTime parsed = FileTime.from(Instant.parse(instant));
128149
assertEquals(ntfsTime, FileTimes.toNtfsTime(parsed));
@@ -179,10 +200,13 @@ public void testNtfsTimeToDate(final String instant, final long ntfsTime) {
179200
}
180201

181202
@ParameterizedTest
182-
@MethodSource("fileTimeToNtfsProvider")
183-
public void testNtfsTimeToFileTime(final String instant, final long ntfsTime) {
184-
final FileTime parsed = FileTime.from(Instant.parse(instant));
185-
assertEquals(parsed, FileTimes.ntfsTimeToFileTime(ntfsTime));
203+
@MethodSource("fileTimeNanoUnitsToNtfsProvider")
204+
public void testNtfsTimeToFileTime(final String instantStr, final long ntfsTime) {
205+
final Instant instant = Instant.parse(instantStr);
206+
final FileTime fileTime = FileTime.from(instant);
207+
assertEquals(instant, fileTime.toInstant()); // sanity check
208+
assertEquals(instant, FileTimes.ntfsTimeToInstant(ntfsTime));
209+
assertEquals(fileTime, FileTimes.ntfsTimeToFileTime(ntfsTime));
186210
}
187211

188212
@Test

0 commit comments

Comments
 (0)