Skip to content

Commit 23d5293

Browse files
authored
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 9024744 commit 23d5293

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
@@ -474,12 +474,7 @@ private List<IndexTemplateMetaData> findTemplates(CreateIndexClusterStateUpdateR
474474
}
475475
}
476476

477-
CollectionUtil.timSort(templateMetadata, new Comparator<IndexTemplateMetaData>() {
478-
@Override
479-
public int compare(IndexTemplateMetaData o1, IndexTemplateMetaData o2) {
480-
return o2.order() - o1.order();
481-
}
482-
});
477+
CollectionUtil.timSort(templateMetadata, Comparator.comparingInt(IndexTemplateMetaData::order).reversed());
483478
return templateMetadata;
484479
}
485480

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
@@ -24,6 +24,7 @@
2424
import org.apache.lucene.util.automaton.CharacterRunAutomaton;
2525
import org.apache.lucene.util.automaton.Operations;
2626
import org.elasticsearch.ElasticsearchParseException;
27+
import org.elasticsearch.common.Numbers;
2728
import org.elasticsearch.common.Strings;
2829
import org.elasticsearch.common.regex.Regex;
2930
import org.elasticsearch.common.unit.TimeValue;
@@ -357,7 +358,7 @@ public static double nodeDoubleValue(Object node) {
357358

358359
public static int nodeIntegerValue(Object node) {
359360
if (node instanceof Number) {
360-
return ((Number) node).intValue();
361+
return Numbers.toIntExact((Number) node);
361362
}
362363
return Integer.parseInt(node.toString());
363364
}
@@ -366,10 +367,7 @@ public static int nodeIntegerValue(Object node, int defaultValue) {
366367
if (node == null) {
367368
return defaultValue;
368369
}
369-
if (node instanceof Number) {
370-
return ((Number) node).intValue();
371-
}
372-
return Integer.parseInt(node.toString());
370+
return nodeIntegerValue(node);
373371
}
374372

375373
public static short nodeShortValue(Object node, short defaultValue) {
@@ -381,7 +379,7 @@ public static short nodeShortValue(Object node, short defaultValue) {
381379

382380
public static short nodeShortValue(Object node) {
383381
if (node instanceof Number) {
384-
return ((Number) node).shortValue();
382+
return Numbers.toShortExact((Number) node);
385383
}
386384
return Short.parseShort(node.toString());
387385
}
@@ -395,7 +393,7 @@ public static byte nodeByteValue(Object node, byte defaultValue) {
395393

396394
public static byte nodeByteValue(Object node) {
397395
if (node instanceof Number) {
398-
return ((Number) node).byteValue();
396+
return Numbers.toByteExact((Number) node);
399397
}
400398
return Byte.parseByte(node.toString());
401399
}
@@ -409,7 +407,7 @@ public static long nodeLongValue(Object node, long defaultValue) {
409407

410408
public static long nodeLongValue(Object node) {
411409
if (node instanceof Number) {
412-
return ((Number) node).longValue();
410+
return Numbers.toLongExact((Number) node);
413411
}
414412
return Long.parseLong(node.toString());
415413
}
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)