--- old/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/arrays/SparseArrayData.java 2016-12-15 11:26:13.000000000 +0100 +++ new/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/arrays/SparseArrayData.java 2016-12-15 11:26:13.000000000 +0100 @@ -36,6 +36,7 @@ * Handle arrays where the index is very large. */ class SparseArrayData extends ArrayData { + /** Maximum size for dense arrays */ static final int MAX_DENSE_LENGTH = 1024 * 1024; /** Underlying array. */ @@ -51,7 +52,7 @@ this(underlying, length, new TreeMap<>()); } - SparseArrayData(final ArrayData underlying, final long length, final TreeMap sparseMap) { + private SparseArrayData(final ArrayData underlying, final long length, final TreeMap sparseMap) { super(length); assert underlying.length() <= length; this.underlying = underlying; @@ -89,38 +90,49 @@ } @Override - public void shiftLeft(final int by) { - underlying.shiftLeft(by); + public ArrayData shiftLeft(final int by) { + underlying = underlying.shiftLeft(by); final TreeMap newSparseMap = new TreeMap<>(); for (final Map.Entry entry : sparseMap.entrySet()) { final long newIndex = entry.getKey() - by; - if (newIndex < maxDenseLength) { - underlying = underlying.set((int) newIndex, entry.getValue(), false); - } else if (newIndex >= 0) { - newSparseMap.put(newIndex, entry.getValue()); + if (newIndex >= 0) { + if (newIndex < maxDenseLength) { + final long oldLength = underlying.length(); + underlying = underlying.ensure(newIndex) + .set((int) newIndex, entry.getValue(), false) + .safeDelete(oldLength, newIndex - 1, false); + } else { + newSparseMap.put(newIndex, entry.getValue()); + } } } sparseMap = newSparseMap; setLength(Math.max(length() - by, 0)); + + return sparseMap.isEmpty() ? underlying : this; } @Override public ArrayData shiftRight(final int by) { final TreeMap newSparseMap = new TreeMap<>(); + // Move elements from underlying to sparse map if necessary final long len = underlying.length(); if (len + by > maxDenseLength) { - for (long i = maxDenseLength - by; i < len; i++) { + // Length of underlying array after shrinking, before right-shifting + final long tempLength = Math.max(0, maxDenseLength - by); + for (long i = tempLength; i < len; i++) { if (underlying.has((int) i)) { newSparseMap.put(i + by, underlying.getObject((int) i)); } } - underlying = underlying.shrink((int) (maxDenseLength - by)); + underlying = underlying.shrink((int) tempLength); + underlying.setLength(tempLength); } - underlying.shiftRight(by); + underlying = underlying.shiftRight(by); for (final Map.Entry entry : sparseMap.entrySet()) { final long newIndex = entry.getKey() + by; @@ -135,14 +147,6 @@ @Override public ArrayData ensure(final long safeIndex) { - // Usually #ensure only needs to be called if safeIndex is greater or equal current length. - // SparseArrayData is an exception as an index smaller than our current length may still - // exceed the underlying ArrayData's capacity. Because of this, SparseArrayData invokes - // its ensure method internally in various places where other ArrayData subclasses don't, - // making it safe for outside uses to only call ensure(safeIndex) if safeIndex >= length. - if (safeIndex < maxDenseLength && underlying.length() <= safeIndex) { - underlying = underlying.ensure(safeIndex); - } if (safeIndex >= length()) { setLength(safeIndex + 1); } @@ -167,8 +171,7 @@ public ArrayData set(final int index, final Object value, final boolean strict) { if (index >= 0 && index < maxDenseLength) { final long oldLength = underlying.length(); - ensure(index); - underlying = underlying.set(index, value, strict).safeDelete(oldLength, index - 1, strict); + underlying = underlying.ensure(index).set(index, value, strict).safeDelete(oldLength, index - 1, strict); setLength(Math.max(underlying.length(), length())); } else { final Long longIndex = indexToKey(index); @@ -183,8 +186,7 @@ public ArrayData set(final int index, final int value, final boolean strict) { if (index >= 0 && index < maxDenseLength) { final long oldLength = underlying.length(); - ensure(index); - underlying = underlying.set(index, value, strict).safeDelete(oldLength, index - 1, strict); + underlying = underlying.ensure(index).set(index, value, strict).safeDelete(oldLength, index - 1, strict); setLength(Math.max(underlying.length(), length())); } else { final Long longIndex = indexToKey(index); @@ -198,8 +200,7 @@ public ArrayData set(final int index, final double value, final boolean strict) { if (index >= 0 && index < maxDenseLength) { final long oldLength = underlying.length(); - ensure(index); - underlying = underlying.set(index, value, strict).safeDelete(oldLength, index - 1, strict); + underlying = underlying.ensure(index).set(index, value, strict).safeDelete(oldLength, index - 1, strict); setLength(Math.max(underlying.length(), length())); } else { final Long longIndex = indexToKey(index);