Skip to content

Commit 1a40b49

Browse files
committed
Fix integer overflows when dealing with templates. (#21628)
The overflows were happening in two places, the parsing of the template that implicitly truncates the `order` when its value does not fall into the `integer` range, and the comparator that sorts templates in ascending order, since it returns `order2-order1`, which might overflow. Closes #21622
1 parent d918e87 commit 1a40b49

File tree

4 files changed

+208
-14
lines changed

4 files changed

+208
-14
lines changed

core/src/main/java/org/elasticsearch/cluster/metadata/MetaDataCreateIndexService.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -464,12 +464,7 @@ private List<IndexTemplateMetaData> findTemplates(CreateIndexClusterStateUpdateR
464464
}
465465
}
466466

467-
CollectionUtil.timSort(templates, new Comparator<IndexTemplateMetaData>() {
468-
@Override
469-
public int compare(IndexTemplateMetaData o1, IndexTemplateMetaData o2) {
470-
return o2.order() - o1.order();
471-
}
472-
});
467+
CollectionUtil.timSort(templates, Comparator.comparingInt(IndexTemplateMetaData::order).reversed());
473468
return templates;
474469
}
475470

core/src/main/java/org/elasticsearch/common/Numbers.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121

2222
import org.apache.lucene.util.BytesRef;
2323

24+
import java.math.BigDecimal;
25+
import java.math.BigInteger;
26+
2427
/**
2528
* A set of utilities for numbers.
2629
*/
@@ -178,4 +181,56 @@ public static boolean isValidDouble(double value) {
178181
}
179182
return true;
180183
}
184+
185+
/** Return the long that {@code n} stores, or throws an exception if the
186+
* stored value cannot be converted to a long that stores the exact same
187+
* value. */
188+
public static long toLongExact(Number n) {
189+
if (n instanceof Byte || n instanceof Short || n instanceof Integer
190+
|| n instanceof Long) {
191+
return n.longValue();
192+
} else if (n instanceof Float || n instanceof Double) {
193+
double d = n.doubleValue();
194+
if (d != Math.round(d)) {
195+
throw new IllegalArgumentException(n + " is not an integer value");
196+
}
197+
return n.longValue();
198+
} else if (n instanceof BigDecimal) {
199+
return ((BigDecimal) n).toBigIntegerExact().longValueExact();
200+
} else if (n instanceof BigInteger) {
201+
return ((BigInteger) n).longValueExact();
202+
} else {
203+
throw new IllegalArgumentException("Cannot check whether [" + n + "] of class [" + n.getClass().getName()
204+
+ "] is actually a long");
205+
}
206+
}
207+
208+
/** Return the int that {@code n} stores, or throws an exception if the
209+
* stored value cannot be converted to an int that stores the exact same
210+
* value. */
211+
public static int toIntExact(Number n) {
212+
return Math.toIntExact(toLongExact(n));
213+
}
214+
215+
/** Return the short that {@code n} stores, or throws an exception if the
216+
* stored value cannot be converted to a short that stores the exact same
217+
* value. */
218+
public static short toShortExact(Number n) {
219+
long l = toLongExact(n);
220+
if (l != (short) l) {
221+
throw new ArithmeticException("short overflow: " + l);
222+
}
223+
return (short) l;
224+
}
225+
226+
/** Return the byte that {@code n} stores, or throws an exception if the
227+
* stored value cannot be converted to a byte that stores the exact same
228+
* value. */
229+
public static byte toByteExact(Number n) {
230+
long l = toLongExact(n);
231+
if (l != (byte) l) {
232+
throw new ArithmeticException("byte overflow: " + l);
233+
}
234+
return (byte) l;
235+
}
181236
}

core/src/main/java/org/elasticsearch/common/xcontent/support/XContentMapValues.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.elasticsearch.common.xcontent.support;
2121

2222
import org.elasticsearch.ElasticsearchParseException;
23+
import org.elasticsearch.common.Numbers;
2324
import org.elasticsearch.common.Strings;
2425
import org.elasticsearch.common.regex.Regex;
2526
import org.elasticsearch.common.unit.TimeValue;
@@ -290,7 +291,7 @@ public static double nodeDoubleValue(Object node) {
290291

291292
public static int nodeIntegerValue(Object node) {
292293
if (node instanceof Number) {
293-
return ((Number) node).intValue();
294+
return Numbers.toIntExact((Number) node);
294295
}
295296
return Integer.parseInt(node.toString());
296297
}
@@ -299,10 +300,7 @@ public static int nodeIntegerValue(Object node, int defaultValue) {
299300
if (node == null) {
300301
return defaultValue;
301302
}
302-
if (node instanceof Number) {
303-
return ((Number) node).intValue();
304-
}
305-
return Integer.parseInt(node.toString());
303+
return nodeIntegerValue(node);
306304
}
307305

308306
public static short nodeShortValue(Object node, short defaultValue) {
@@ -314,7 +312,7 @@ public static short nodeShortValue(Object node, short defaultValue) {
314312

315313
public static short nodeShortValue(Object node) {
316314
if (node instanceof Number) {
317-
return ((Number) node).shortValue();
315+
return Numbers.toShortExact((Number) node);
318316
}
319317
return Short.parseShort(node.toString());
320318
}
@@ -328,7 +326,7 @@ public static byte nodeByteValue(Object node, byte defaultValue) {
328326

329327
public static byte nodeByteValue(Object node) {
330328
if (node instanceof Number) {
331-
return ((Number) node).byteValue();
329+
return Numbers.toByteExact((Number) node);
332330
}
333331
return Byte.parseByte(node.toString());
334332
}
@@ -342,7 +340,7 @@ public static long nodeLongValue(Object node, long defaultValue) {
342340

343341
public static long nodeLongValue(Object node) {
344342
if (node instanceof Number) {
345-
return ((Number) node).longValue();
343+
return Numbers.toLongExact((Number) node);
346344
}
347345
return Long.parseLong(node.toString());
348346
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.common;
21+
22+
import org.elasticsearch.test.ESTestCase;
23+
24+
import java.math.BigDecimal;
25+
import java.math.BigInteger;
26+
import java.util.concurrent.atomic.AtomicInteger;
27+
28+
public class NumbersTests extends ESTestCase {
29+
30+
public void testToLongExact() {
31+
assertEquals(3L, Numbers.toLongExact(Long.valueOf(3L)));
32+
assertEquals(3L, Numbers.toLongExact(Integer.valueOf(3)));
33+
assertEquals(3L, Numbers.toLongExact(Short.valueOf((short) 3)));
34+
assertEquals(3L, Numbers.toLongExact(Byte.valueOf((byte) 3)));
35+
assertEquals(3L, Numbers.toLongExact(3d));
36+
assertEquals(3L, Numbers.toLongExact(3f));
37+
assertEquals(3L, Numbers.toLongExact(BigInteger.valueOf(3L)));
38+
assertEquals(3L, Numbers.toLongExact(BigDecimal.valueOf(3L)));
39+
40+
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
41+
() -> Numbers.toLongExact(3.1d));
42+
assertEquals("3.1 is not an integer value", e.getMessage());
43+
e = expectThrows(IllegalArgumentException.class,
44+
() -> Numbers.toLongExact(Double.NaN));
45+
assertEquals("NaN is not an integer value", e.getMessage());
46+
e = expectThrows(IllegalArgumentException.class,
47+
() -> Numbers.toLongExact(Double.POSITIVE_INFINITY));
48+
assertEquals("Infinity is not an integer value", e.getMessage());
49+
e = expectThrows(IllegalArgumentException.class,
50+
() -> Numbers.toLongExact(3.1f));
51+
assertEquals("3.1 is not an integer value", e.getMessage());
52+
e = expectThrows(IllegalArgumentException.class,
53+
() -> Numbers.toLongExact(new AtomicInteger(3))); // not supported
54+
assertEquals("Cannot check whether [3] of class [java.util.concurrent.atomic.AtomicInteger] is actually a long", e.getMessage());
55+
}
56+
57+
public void testToIntExact() {
58+
assertEquals(3L, Numbers.toIntExact(Long.valueOf(3L)));
59+
assertEquals(3L, Numbers.toIntExact(Integer.valueOf(3)));
60+
assertEquals(3L, Numbers.toIntExact(Short.valueOf((short) 3)));
61+
assertEquals(3L, Numbers.toIntExact(Byte.valueOf((byte) 3)));
62+
assertEquals(3L, Numbers.toIntExact(3d));
63+
assertEquals(3L, Numbers.toIntExact(3f));
64+
assertEquals(3L, Numbers.toIntExact(BigInteger.valueOf(3L)));
65+
assertEquals(3L, Numbers.toIntExact(BigDecimal.valueOf(3L)));
66+
67+
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
68+
() -> Numbers.toIntExact(3.1d));
69+
assertEquals("3.1 is not an integer value", e.getMessage());
70+
e = expectThrows(IllegalArgumentException.class,
71+
() -> Numbers.toLongExact(Double.NaN));
72+
assertEquals("NaN is not an integer value", e.getMessage());
73+
e = expectThrows(IllegalArgumentException.class,
74+
() -> Numbers.toLongExact(Double.POSITIVE_INFINITY));
75+
assertEquals("Infinity is not an integer value", e.getMessage());
76+
e = expectThrows(IllegalArgumentException.class,
77+
() -> Numbers.toIntExact(3.1f));
78+
assertEquals("3.1 is not an integer value", e.getMessage());
79+
ArithmeticException ae = expectThrows(ArithmeticException.class,
80+
() -> Numbers.toIntExact(1L << 40));
81+
assertEquals("integer overflow", ae.getMessage());
82+
e = expectThrows(IllegalArgumentException.class,
83+
() -> Numbers.toIntExact(new AtomicInteger(3))); // not supported
84+
assertEquals("Cannot check whether [3] of class [java.util.concurrent.atomic.AtomicInteger] is actually a long", e.getMessage());
85+
}
86+
87+
public void testToShortExact() {
88+
assertEquals(3L, Numbers.toShortExact(Long.valueOf(3L)));
89+
assertEquals(3L, Numbers.toShortExact(Integer.valueOf(3)));
90+
assertEquals(3L, Numbers.toShortExact(Short.valueOf((short) 3)));
91+
assertEquals(3L, Numbers.toShortExact(Byte.valueOf((byte) 3)));
92+
assertEquals(3L, Numbers.toShortExact(3d));
93+
assertEquals(3L, Numbers.toShortExact(3f));
94+
assertEquals(3L, Numbers.toShortExact(BigInteger.valueOf(3L)));
95+
assertEquals(3L, Numbers.toShortExact(BigDecimal.valueOf(3L)));
96+
97+
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
98+
() -> Numbers.toShortExact(3.1d));
99+
assertEquals("3.1 is not an integer value", e.getMessage());
100+
e = expectThrows(IllegalArgumentException.class,
101+
() -> Numbers.toLongExact(Double.NaN));
102+
assertEquals("NaN is not an integer value", e.getMessage());
103+
e = expectThrows(IllegalArgumentException.class,
104+
() -> Numbers.toLongExact(Double.POSITIVE_INFINITY));
105+
assertEquals("Infinity is not an integer value", e.getMessage());
106+
e = expectThrows(IllegalArgumentException.class,
107+
() -> Numbers.toShortExact(3.1f));
108+
assertEquals("3.1 is not an integer value", e.getMessage());
109+
ArithmeticException ae = expectThrows(ArithmeticException.class,
110+
() -> Numbers.toShortExact(100000));
111+
assertEquals("short overflow: " + 100000, ae.getMessage());
112+
e = expectThrows(IllegalArgumentException.class,
113+
() -> Numbers.toShortExact(new AtomicInteger(3))); // not supported
114+
assertEquals("Cannot check whether [3] of class [java.util.concurrent.atomic.AtomicInteger] is actually a long", e.getMessage());
115+
}
116+
117+
public void testToByteExact() {
118+
assertEquals(3L, Numbers.toByteExact(Long.valueOf(3L)));
119+
assertEquals(3L, Numbers.toByteExact(Integer.valueOf(3)));
120+
assertEquals(3L, Numbers.toByteExact(Short.valueOf((short) 3)));
121+
assertEquals(3L, Numbers.toByteExact(Byte.valueOf((byte) 3)));
122+
assertEquals(3L, Numbers.toByteExact(3d));
123+
assertEquals(3L, Numbers.toByteExact(3f));
124+
assertEquals(3L, Numbers.toByteExact(BigInteger.valueOf(3L)));
125+
assertEquals(3L, Numbers.toByteExact(BigDecimal.valueOf(3L)));
126+
127+
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
128+
() -> Numbers.toByteExact(3.1d));
129+
assertEquals("3.1 is not an integer value", e.getMessage());
130+
e = expectThrows(IllegalArgumentException.class,
131+
() -> Numbers.toLongExact(Double.NaN));
132+
assertEquals("NaN is not an integer value", e.getMessage());
133+
e = expectThrows(IllegalArgumentException.class,
134+
() -> Numbers.toLongExact(Double.POSITIVE_INFINITY));
135+
assertEquals("Infinity is not an integer value", e.getMessage());
136+
e = expectThrows(IllegalArgumentException.class,
137+
() -> Numbers.toByteExact(3.1f));
138+
assertEquals("3.1 is not an integer value", e.getMessage());
139+
ArithmeticException ae = expectThrows(ArithmeticException.class,
140+
() -> Numbers.toByteExact(300));
141+
assertEquals("byte overflow: " + 300, ae.getMessage());
142+
e = expectThrows(IllegalArgumentException.class,
143+
() -> Numbers.toByteExact(new AtomicInteger(3))); // not supported
144+
assertEquals("Cannot check whether [3] of class [java.util.concurrent.atomic.AtomicInteger] is actually a long", e.getMessage());
145+
}
146+
}

0 commit comments

Comments
 (0)