mirror of
https://github.com/lightbend/config.git
synced 2025-04-26 04:50:29 +08:00
Throw an exception if size-in-bytes values are out of Long range
Fixes #170
This commit is contained in:
parent
95b31ccd57
commit
6311ef8d4d
7
HOCON.md
7
HOCON.md
@ -1265,6 +1265,13 @@ spec copies that. You can certainly find examples of mapping these
|
||||
to powers of ten, though. If you don't like ambiguity, don't use
|
||||
the single-letter abbreviations.
|
||||
|
||||
Note: any value in zetta/zebi or yotta/yobi will overflow a 64-bit
|
||||
integer, and of course large-enough values in any of the units may
|
||||
overflow. Most real-world APIs and apps will not support byte
|
||||
counts that overflow a 64-bit integer. The huge units are provided
|
||||
just to be complete but probably aren't useful in practice. At
|
||||
least not in 2014.
|
||||
|
||||
### Config object merging and file merging
|
||||
|
||||
It may be useful to offer a method to merge two objects. If such a
|
||||
|
@ -5,6 +5,8 @@ package com.typesafe.config.impl;
|
||||
|
||||
import java.io.ObjectStreamException;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@ -575,19 +577,13 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
final String prefix;
|
||||
final int powerOf;
|
||||
final int power;
|
||||
final long bytes;
|
||||
final BigInteger bytes;
|
||||
|
||||
MemoryUnit(String prefix, int powerOf, int power) {
|
||||
this.prefix = prefix;
|
||||
this.powerOf = powerOf;
|
||||
this.power = power;
|
||||
int i = power;
|
||||
long bytes = 1;
|
||||
while (i > 0) {
|
||||
bytes *= powerOf;
|
||||
--i;
|
||||
}
|
||||
this.bytes = bytes;
|
||||
this.bytes = BigInteger.valueOf(powerOf).pow(power);
|
||||
}
|
||||
|
||||
private static Map<String, MemoryUnit> makeUnitsMap() {
|
||||
@ -628,6 +624,12 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isValidLong(BigInteger v) {
|
||||
BigInteger max = BigInteger.valueOf(Long.MAX_VALUE);
|
||||
BigInteger min = BigInteger.valueOf(Long.MIN_VALUE);
|
||||
return max.compareTo(v) >= 0 && min.compareTo(v) <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a size-in-bytes string. If no units are specified in the string,
|
||||
* it is assumed to be in bytes. The returned value is in bytes. The purpose
|
||||
@ -667,13 +669,22 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
}
|
||||
|
||||
try {
|
||||
BigInteger result;
|
||||
// if the string is purely digits, parse as an integer to avoid
|
||||
// possible precision loss; otherwise as a double.
|
||||
if (numberString.matches("[0-9]+")) {
|
||||
return Long.parseLong(numberString) * units.bytes;
|
||||
Long v = Long.parseLong(numberString);
|
||||
result = units.bytes.multiply(BigInteger.valueOf(v));
|
||||
} else {
|
||||
return (long) (Double.parseDouble(numberString) * units.bytes);
|
||||
Double v = Double.parseDouble(numberString);
|
||||
BigDecimal resultDecimal = (new BigDecimal(units.bytes)).multiply(BigDecimal.valueOf(v));
|
||||
result = resultDecimal.toBigInteger();
|
||||
}
|
||||
if (isValidLong(result))
|
||||
return result.longValue();
|
||||
else
|
||||
throw new ConfigException.BadValue(originForException, pathForException,
|
||||
"size-in-bytes value is out of range for a 64-bit long: '" + input + "'");
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ConfigException.BadValue(originForException, pathForException,
|
||||
"Could not parse size-in-bytes number '" + numberString + "'");
|
||||
|
@ -50,11 +50,11 @@ class UnitParserTest extends TestUtils {
|
||||
|
||||
val conf = parseConfig("foo = 1d")
|
||||
assertEquals("could get 1d from conf as days",
|
||||
1L, conf.getDuration("foo", TimeUnit.DAYS))
|
||||
1L, conf.getDuration("foo", TimeUnit.DAYS))
|
||||
assertEquals("could get 1d from conf as nanos",
|
||||
dayInNanos, conf.getNanoseconds("foo"))
|
||||
dayInNanos, conf.getNanoseconds("foo"))
|
||||
assertEquals("could get 1d from conf as millis",
|
||||
TimeUnit.DAYS.toMillis(1), conf.getMilliseconds("foo"))
|
||||
TimeUnit.DAYS.toMillis(1), conf.getMilliseconds("foo"))
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -88,7 +88,7 @@ class UnitParserTest extends TestUtils {
|
||||
}
|
||||
|
||||
var result = 1024L * 1024 * 1024
|
||||
for (unit <- Seq("tebi", "pebi", "exbi", "zebi", "yobi")) {
|
||||
for (unit <- Seq("tebi", "pebi", "exbi")) {
|
||||
val first = unit.substring(0, 1).toUpperCase()
|
||||
result = result * 1024;
|
||||
assertEquals(result, parseMem("1" + first))
|
||||
@ -99,7 +99,7 @@ class UnitParserTest extends TestUtils {
|
||||
}
|
||||
|
||||
result = 1000L * 1000 * 1000
|
||||
for (unit <- Seq("tera", "peta", "exa", "zetta", "yotta")) {
|
||||
for (unit <- Seq("tera", "peta", "exa")) {
|
||||
val first = unit.substring(0, 1).toUpperCase()
|
||||
result = result * 1000;
|
||||
assertEquals(result, parseMem("1" + first + "B"))
|
||||
@ -119,4 +119,40 @@ class UnitParserTest extends TestUtils {
|
||||
}
|
||||
assertTrue(e2.getMessage().contains("size-in-bytes number"))
|
||||
}
|
||||
|
||||
// later on we'll want to check this with BigInteger version of getBytes
|
||||
@Test
|
||||
def parseHugeMemorySizes(): Unit = {
|
||||
def parseMem(s: String) = SimpleConfig.parseBytes(s, fakeOrigin(), "test")
|
||||
def assertOutOfRange(s: String) = {
|
||||
val fail = intercept[ConfigException.BadValue] {
|
||||
parseMem(s)
|
||||
}
|
||||
assertTrue("number was too big", fail.getMessage.contains("out of range"))
|
||||
}
|
||||
var result = 1024L * 1024 * 1024
|
||||
for (unit <- Seq("zebi", "yobi")) {
|
||||
val first = unit.substring(0, 1).toUpperCase()
|
||||
assertOutOfRange("1" + first)
|
||||
assertOutOfRange("1" + first + "i")
|
||||
assertOutOfRange("1" + first + "iB")
|
||||
assertOutOfRange("1" + unit + "byte")
|
||||
assertOutOfRange("1" + unit + "bytes")
|
||||
assertOutOfRange("1.1" + first)
|
||||
assertOutOfRange("-1" + first)
|
||||
}
|
||||
|
||||
result = 1000L * 1000 * 1000
|
||||
for (unit <- Seq("zetta", "yotta")) {
|
||||
val first = unit.substring(0, 1).toUpperCase()
|
||||
assertOutOfRange("1" + first + "B")
|
||||
assertOutOfRange("1" + unit + "byte")
|
||||
assertOutOfRange("1" + unit + "bytes")
|
||||
assertOutOfRange("1.1" + first + "B")
|
||||
assertOutOfRange("-1" + first + "B")
|
||||
}
|
||||
|
||||
assertOutOfRange("1000 exabytes")
|
||||
assertOutOfRange("10000000 petabytes")
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user