Merge pull request #7509 from eclipse/jetty-10.0.x-7496-Trie-Overflow
Jetty 10.0.x : fix tries mistakenly throwing ArrayIndexOutOfBoundsException
This commit is contained in:
commit
66d3165a8c
|
@ -71,7 +71,7 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
||||||
* the 16 bit indexes can overflow and the trie
|
* the 16 bit indexes can overflow and the trie
|
||||||
* cannot find existing entries anymore.
|
* cannot find existing entries anymore.
|
||||||
*/
|
*/
|
||||||
private static final int MAX_CAPACITY = Character.MAX_VALUE;
|
private static final int MAX_CAPACITY = Character.MAX_VALUE - 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Trie rows in a single array which allows a lookup of row,character
|
* The Trie rows in a single array which allows a lookup of row,character
|
||||||
|
@ -111,9 +111,9 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
||||||
super(caseSensitive);
|
super(caseSensitive);
|
||||||
if (capacity > MAX_CAPACITY)
|
if (capacity > MAX_CAPACITY)
|
||||||
throw new IllegalArgumentException("ArrayTernaryTrie maximum capacity overflow (" + capacity + " > " + MAX_CAPACITY + ")");
|
throw new IllegalArgumentException("ArrayTernaryTrie maximum capacity overflow (" + capacity + " > " + MAX_CAPACITY + ")");
|
||||||
_value = (V[])new Object[capacity];
|
_value = (V[])new Object[capacity + 1];
|
||||||
_tree = new char[capacity * ROW_SIZE];
|
_tree = new char[(capacity + 1) * ROW_SIZE];
|
||||||
_key = new String[capacity];
|
_key = new String[capacity + 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -130,26 +130,28 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
||||||
{
|
{
|
||||||
int t = 0;
|
int t = 0;
|
||||||
int limit = s.length();
|
int limit = s.length();
|
||||||
|
if (limit > MAX_CAPACITY)
|
||||||
|
return false;
|
||||||
int last = 0;
|
int last = 0;
|
||||||
for (int k = 0; k < limit; k++)
|
for (int k = 0; k < limit; k++)
|
||||||
{
|
{
|
||||||
char c = s.charAt(k);
|
char c = s.charAt(k);
|
||||||
if (isCaseInsensitive())
|
if (isCaseInsensitive() && c < 128)
|
||||||
c = StringUtil.asciiToLowerCase(c);
|
c = StringUtil.asciiToLowerCase(c);
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
if (_rows == MAX_CAPACITY)
|
||||||
|
return false;
|
||||||
|
|
||||||
int row = ROW_SIZE * t;
|
int row = ROW_SIZE * t;
|
||||||
|
|
||||||
// Do we need to create the new row?
|
// Do we need to create the new row?
|
||||||
if (t == _rows)
|
if (t == _rows)
|
||||||
{
|
{
|
||||||
_rows++;
|
_rows = (char)MathUtils.cappedAdd(_rows, 1, _key.length);
|
||||||
if (_rows > _key.length)
|
if (_rows == _key.length)
|
||||||
{
|
|
||||||
_rows--;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
_tree[row] = c;
|
_tree[row] = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,12 +179,9 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
||||||
// Do we need to create the new row?
|
// Do we need to create the new row?
|
||||||
if (t == _rows)
|
if (t == _rows)
|
||||||
{
|
{
|
||||||
_rows++;
|
if (_rows == _key.length)
|
||||||
if (_rows > _key.length)
|
|
||||||
{
|
|
||||||
_rows--;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
_rows++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put the key and value
|
// Put the key and value
|
||||||
|
@ -199,7 +198,7 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
||||||
for (int i = 0; i < len; )
|
for (int i = 0; i < len; )
|
||||||
{
|
{
|
||||||
char c = s.charAt(offset + i++);
|
char c = s.charAt(offset + i++);
|
||||||
if (isCaseInsensitive())
|
if (isCaseInsensitive() && c < 128)
|
||||||
c = StringUtil.asciiToLowerCase(c);
|
c = StringUtil.asciiToLowerCase(c);
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
|
@ -257,7 +256,7 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (V)_value[t];
|
return _value[t];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -281,7 +280,7 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
||||||
{
|
{
|
||||||
char c = s.charAt(offset++);
|
char c = s.charAt(offset++);
|
||||||
len--;
|
len--;
|
||||||
if (isCaseInsensitive())
|
if (isCaseInsensitive() && c < 128)
|
||||||
c = StringUtil.asciiToLowerCase(c);
|
c = StringUtil.asciiToLowerCase(c);
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
|
@ -312,7 +311,7 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
||||||
break loop;
|
break loop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (V)_value[node];
|
return _value[node];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -369,7 +368,7 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
||||||
break loop;
|
break loop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (V)_value[node];
|
return _value[node];
|
||||||
}
|
}
|
||||||
|
|
||||||
private V getBest(int t, ByteBuffer b, int offset, int len)
|
private V getBest(int t, ByteBuffer b, int offset, int len)
|
||||||
|
@ -380,6 +379,9 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
||||||
loop:
|
loop:
|
||||||
for (int i = 0; i < len; i++)
|
for (int i = 0; i < len; i++)
|
||||||
{
|
{
|
||||||
|
if (o + i >= b.limit())
|
||||||
|
return null;
|
||||||
|
|
||||||
byte c = (byte)(b.get(o + i) & 0x7f);
|
byte c = (byte)(b.get(o + i) & 0x7f);
|
||||||
if (isCaseInsensitive())
|
if (isCaseInsensitive())
|
||||||
c = StringUtil.asciiToLowerCase(c);
|
c = StringUtil.asciiToLowerCase(c);
|
||||||
|
@ -412,7 +414,7 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
||||||
break loop;
|
break loop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (V)_value[node];
|
return _value[node];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -430,7 +432,7 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
||||||
buf.append(',');
|
buf.append(',');
|
||||||
buf.append(_key[r]);
|
buf.append(_key[r]);
|
||||||
buf.append('=');
|
buf.append('=');
|
||||||
buf.append(String.valueOf(_value[r]));
|
buf.append(_value[r]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buf.append('}');
|
buf.append('}');
|
||||||
|
@ -442,7 +444,7 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
||||||
{
|
{
|
||||||
Set<String> keys = new HashSet<>();
|
Set<String> keys = new HashSet<>();
|
||||||
|
|
||||||
for (int r = 0; r <= _rows; r++)
|
for (int r = 0; r < _rows; r++)
|
||||||
{
|
{
|
||||||
if (_key[r] != null && _value[r] != null)
|
if (_key[r] != null && _value[r] != null)
|
||||||
keys.add(_key[r]);
|
keys.add(_key[r]);
|
||||||
|
@ -453,7 +455,7 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
||||||
public int size()
|
public int size()
|
||||||
{
|
{
|
||||||
int s = 0;
|
int s = 0;
|
||||||
for (int r = 0; r <= _rows; r++)
|
for (int r = 0; r < _rows; r++)
|
||||||
{
|
{
|
||||||
if (_key[r] != null && _value[r] != null)
|
if (_key[r] != null && _value[r] != null)
|
||||||
s++;
|
s++;
|
||||||
|
@ -463,7 +465,7 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
||||||
|
|
||||||
public boolean isEmpty()
|
public boolean isEmpty()
|
||||||
{
|
{
|
||||||
for (int r = 0; r <= _rows; r++)
|
for (int r = 0; r < _rows; r++)
|
||||||
{
|
{
|
||||||
if (_key[r] != null && _value[r] != null)
|
if (_key[r] != null && _value[r] != null)
|
||||||
return false;
|
return false;
|
||||||
|
@ -474,7 +476,7 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
||||||
public Set<Map.Entry<String, V>> entrySet()
|
public Set<Map.Entry<String, V>> entrySet()
|
||||||
{
|
{
|
||||||
Set<Map.Entry<String, V>> entries = new HashSet<>();
|
Set<Map.Entry<String, V>> entries = new HashSet<>();
|
||||||
for (int r = 0; r <= _rows; r++)
|
for (int r = 0; r < _rows; r++)
|
||||||
{
|
{
|
||||||
if (_key[r] != null && _value[r] != null)
|
if (_key[r] != null && _value[r] != null)
|
||||||
entries.add(new AbstractMap.SimpleEntry<>(_key[r], _value[r]));
|
entries.add(new AbstractMap.SimpleEntry<>(_key[r], _value[r]));
|
||||||
|
@ -559,7 +561,10 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
||||||
boolean added = _trie.put(s, v);
|
boolean added = _trie.put(s, v);
|
||||||
while (!added && _growby > 0)
|
while (!added && _growby > 0)
|
||||||
{
|
{
|
||||||
ArrayTernaryTrie<V> bigger = new ArrayTernaryTrie<>(_trie.isCaseInsensitive(), _trie._key.length + _growby);
|
int newCapacity = _trie._key.length + _growby;
|
||||||
|
if (newCapacity > MAX_CAPACITY)
|
||||||
|
return false;
|
||||||
|
ArrayTernaryTrie<V> bigger = new ArrayTernaryTrie<>(_trie.isCaseInsensitive(), newCapacity);
|
||||||
for (Map.Entry<String, V> entry : _trie.entrySet())
|
for (Map.Entry<String, V> entry : _trie.entrySet())
|
||||||
{
|
{
|
||||||
bigger.put(entry.getKey(), entry.getValue());
|
bigger.put(entry.getKey(), entry.getValue());
|
||||||
|
|
|
@ -450,6 +450,9 @@ class ArrayTrie<V> extends AbstractTrie<V>
|
||||||
int pos = b.position() + offset;
|
int pos = b.position() + offset;
|
||||||
for (int i = 0; i < len; i++)
|
for (int i = 0; i < len; i++)
|
||||||
{
|
{
|
||||||
|
if (pos >= b.limit())
|
||||||
|
return null;
|
||||||
|
|
||||||
byte c = b.get(pos++);
|
byte c = b.get(pos++);
|
||||||
int next = lookup(row, (char)(c & 0xff));
|
int next = lookup(row, (char)(c & 0xff));
|
||||||
if (next < 0)
|
if (next < 0)
|
||||||
|
|
|
@ -57,4 +57,24 @@ public class MathUtils
|
||||||
return Long.MAX_VALUE;
|
return Long.MAX_VALUE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the sum of its arguments, capping to {@code maxValue}.
|
||||||
|
*
|
||||||
|
* @param a the first value
|
||||||
|
* @param b the second value
|
||||||
|
* @return the sum of the values, capped to {@code maxValue}
|
||||||
|
*/
|
||||||
|
public static int cappedAdd(int a, int b, int maxValue)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int sum = Math.addExact(a, b);
|
||||||
|
return Math.min(sum, maxValue);
|
||||||
|
}
|
||||||
|
catch (ArithmeticException x)
|
||||||
|
{
|
||||||
|
return maxValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.util;
|
package org.eclipse.jetty.util;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -29,7 +31,10 @@ import static org.eclipse.jetty.util.AbstractTrie.requiredCapacity;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.hamcrest.Matchers.nullValue;
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
@ -110,6 +115,48 @@ public class TrieTest
|
||||||
return impls.stream().map(Arguments::of);
|
return impls.stream().map(Arguments::of);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("implementations")
|
||||||
|
public void testOverflow(AbstractTrie<Integer> trie) throws Exception
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (++i > 10000)
|
||||||
|
break; // must not be fixed size
|
||||||
|
if (!trie.put("prefix" + i, i))
|
||||||
|
{
|
||||||
|
String key = "prefix" + i;
|
||||||
|
|
||||||
|
// Assert that all keys can be gotten.
|
||||||
|
for (String k : trie.keySet())
|
||||||
|
{
|
||||||
|
assertNotNull(trie.get(k));
|
||||||
|
assertNotNull(trie.get(toAsciiDirectByteBuffer(k, 0))); // has to be a direct buffer
|
||||||
|
assertNull(trie.get(toAsciiDirectByteBuffer(k, k.length()))); // has to be a direct buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that all getBest() variants do work on full tries.
|
||||||
|
assertNotNull(trie.getBest(key), "key=" + key);
|
||||||
|
assertNotNull(trie.getBest(key.getBytes(StandardCharsets.US_ASCII), 0, key.length()), "key=" + key);
|
||||||
|
assertNotNull(trie.getBest(toAsciiDirectByteBuffer(key, 0), 0, key.length()), "key=" + key); // has to be a direct buffer
|
||||||
|
assertNull(trie.getBest(toAsciiDirectByteBuffer(key, key.length()), 0, key.length()), "key=" + key); // has to be a direct buffer
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trie instanceof ArrayTrie || trie instanceof ArrayTernaryTrie)
|
||||||
|
assertFalse(trie.put("overflow", 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ByteBuffer toAsciiDirectByteBuffer(String s, int pos)
|
||||||
|
{
|
||||||
|
ByteBuffer bb = ByteBuffer.allocateDirect(s.length());
|
||||||
|
bb.put(s.getBytes(StandardCharsets.US_ASCII));
|
||||||
|
bb.position(pos);
|
||||||
|
return bb;
|
||||||
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource("implementations")
|
@MethodSource("implementations")
|
||||||
public void testKeySet(AbstractTrie<Integer> trie) throws Exception
|
public void testKeySet(AbstractTrie<Integer> trie) throws Exception
|
||||||
|
@ -295,6 +342,7 @@ public class TrieTest
|
||||||
testGetString(trie);
|
testGetString(trie);
|
||||||
testGetBestArray(trie);
|
testGetBestArray(trie);
|
||||||
testGetBestBuffer(trie);
|
testGetBestBuffer(trie);
|
||||||
|
assertNull(trie.getBest("Large: This is a really large key and should blow the maximum size of the array trie as lots of nodes should already be used."));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Reference in New Issue