diff --git a/community/values/src/main/java/org/neo4j/values/storable/UTF8StringValue.java b/community/values/src/main/java/org/neo4j/values/storable/UTF8StringValue.java index 591f9f44de7e7..b9d92c42f9c2d 100644 --- a/community/values/src/main/java/org/neo4j/values/storable/UTF8StringValue.java +++ b/community/values/src/main/java/org/neo4j/values/storable/UTF8StringValue.java @@ -158,21 +158,21 @@ public int computeHash() @Override public TextValue substring( int start, int length ) { - int s = Math.min( start, length() ); - int e = Math.min( s + length, length() ); - byte[] values = bytes; - if ( s >= e ) + if ( start < 0 || length < 0 ) { - return StringValue.EMTPY; + throw new IndexOutOfBoundsException( "Cannot handle negative start index nor negative length" ); } + + int end = start + length; + byte[] values = bytes; int count = 0, byteStart = -1, byteEnd = -1, i = offset, len = offset + byteLength; while ( i < len ) { - if ( count == s ) + if ( count == start ) { byteStart = i; } - if ( count == e ) + if ( count == end ) { byteEnd = i; break; @@ -196,9 +196,10 @@ public TextValue substring( int start, int length ) { byteEnd = len; } - - assert byteStart >= 0; - assert byteEnd >= byteStart; + if ( byteStart < 0 ) + { + return StringValue.EMTPY; + } return new UTF8StringValue( values, byteStart, byteEnd - byteStart ); } diff --git a/community/values/src/main/java/org/neo4j/values/storable/Values.java b/community/values/src/main/java/org/neo4j/values/storable/Values.java index fbedf5be4ddd4..d510138964a12 100644 --- a/community/values/src/main/java/org/neo4j/values/storable/Values.java +++ b/community/values/src/main/java/org/neo4j/values/storable/Values.java @@ -48,7 +48,7 @@ public final class Values public static final Value MAX_STRING = Values.booleanValue( false ); public static final BooleanValue TRUE = Values.booleanValue( true ); public static final BooleanValue FALSE = Values.booleanValue( false ); - public static final TextValue EMPTY_STRING = Values.stringValue( "" ); + public static final TextValue EMPTY_STRING = StringValue.EMTPY; public static final DoubleValue E = Values.doubleValue( Math.E ); public static final DoubleValue PI = Values.doubleValue( Math.PI ); public static final ArrayValue EMPTY_SHORT_ARRAY = Values.shortArray( new short[0] ); diff --git a/community/values/src/test/java/org/neo4j/values/storable/UTF8StringValueTest.java b/community/values/src/test/java/org/neo4j/values/storable/UTF8StringValueTest.java index 3f1f0d9ac2260..4acb84cb83e15 100644 --- a/community/values/src/test/java/org/neo4j/values/storable/UTF8StringValueTest.java +++ b/community/values/src/test/java/org/neo4j/values/storable/UTF8StringValueTest.java @@ -19,11 +19,12 @@ */ package org.neo4j.values.storable; +import org.junit.Rule; import org.junit.Test; - -import java.nio.charset.StandardCharsets; +import org.junit.rules.ExpectedException; import static java.lang.String.format; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.neo4j.values.storable.Values.stringValue; @@ -31,6 +32,9 @@ public class UTF8StringValueTest { + @Rule + public ExpectedException exception = ExpectedException.none(); + private String[] strings = {"", "1337", " ", "普通话/普通話", "\uD83D\uDE21", " a b c ", "䤹᳽", "熨", "ۼ", "ⲹ楡톜ഷۢ⼈늉₭샺ጚ砧攡跿家䯶鲏⬖돛犽ۼ", " 㺂࿝鋦毠",//first character is a thin space, @@ -43,7 +47,7 @@ public void shouldHandleDifferentTypesOfStrings() for ( String string : strings ) { TextValue stringValue = stringValue( string ); - byte[] bytes = string.getBytes( StandardCharsets.UTF_8 ); + byte[] bytes = string.getBytes( UTF_8 ); TextValue utf8 = utf8Value( bytes ); assertSame( stringValue, utf8 ); } @@ -55,7 +59,7 @@ public void shouldTrimDifferentTypesOfStrings() for ( String string : strings ) { TextValue stringValue = stringValue( string ); - byte[] bytes = string.getBytes( StandardCharsets.UTF_8 ); + byte[] bytes = string.getBytes( UTF_8 ); TextValue utf8 = utf8Value( bytes ); assertSame( stringValue.trim(), utf8.trim() ); } @@ -67,7 +71,7 @@ public void shouldLTrimDifferentTypesOfStrings() for ( String string : strings ) { TextValue stringValue = stringValue( string ); - byte[] bytes = string.getBytes( StandardCharsets.UTF_8 ); + byte[] bytes = string.getBytes( UTF_8 ); TextValue utf8 = utf8Value( bytes ); assertSame( stringValue.ltrim(), utf8.ltrim() ); } @@ -78,7 +82,7 @@ public void trimShouldBeSameAsLtrimAndRtrim() { for ( String string : strings ) { - TextValue utf8 = utf8Value( string.getBytes( StandardCharsets.UTF_8 ) ); + TextValue utf8 = utf8Value( string.getBytes( UTF_8 ) ); assertSame( utf8.trim(), utf8.ltrim().rtrim() ); } } @@ -87,7 +91,7 @@ public void trimShouldBeSameAsLtrimAndRtrim() public void shouldSubstring() { String string = "ü"; - TextValue utf8 = utf8Value( string.getBytes( StandardCharsets.UTF_8 ) ); + TextValue utf8 = utf8Value( string.getBytes( UTF_8 ) ); assertThat( utf8.substring( 0, 1 ).stringValue(), equalTo( "ü" ) ); } @@ -97,7 +101,7 @@ public void shouldRTrimDifferentTypesOfStrings() for ( String string : strings ) { TextValue stringValue = stringValue( string ); - byte[] bytes = string.getBytes( StandardCharsets.UTF_8 ); + byte[] bytes = string.getBytes( UTF_8 ); TextValue utf8 = utf8Value( bytes ); assertSame( stringValue.rtrim(), utf8.rtrim() ); } @@ -111,10 +115,10 @@ public void shouldCompareTo() for ( String string2 : strings ) { - int x = stringValue( string1 ).compareTo( utf8Value( string2.getBytes( StandardCharsets.UTF_8 ) ) ); - int y = utf8Value( string1.getBytes( StandardCharsets.UTF_8 ) ).compareTo( stringValue( string2 ) ); - int z = utf8Value( string1.getBytes( StandardCharsets.UTF_8 ) ) - .compareTo( utf8Value( string2.getBytes( StandardCharsets.UTF_8 ) ) ); + int x = stringValue( string1 ).compareTo( utf8Value( string2.getBytes( UTF_8 ) ) ); + int y = utf8Value( string1.getBytes( UTF_8 ) ).compareTo( stringValue( string2 ) ); + int z = utf8Value( string1.getBytes( UTF_8 ) ) + .compareTo( utf8Value( string2.getBytes( UTF_8 ) ) ); assertThat( x, equalTo( y ) ); assertThat( x, equalTo( z ) ); @@ -126,7 +130,7 @@ public void shouldCompareTo() public void shouldHandleOffset() { // Given - byte[] bytes = "abcdefg".getBytes( StandardCharsets.UTF_8 ); + byte[] bytes = "abcdefg".getBytes( UTF_8 ); // When TextValue textValue = utf8Value( bytes, 3, 2 ); @@ -144,4 +148,56 @@ private void assertSame( TextValue lhs, TextValue rhs ) assertThat( format( "%s.hashCode != %s.hashCode", rhs, lhs ), lhs.hashCode(), equalTo( rhs.hashCode() ) ); assertThat( lhs, equalTo( rhs ) ); } + + @Test + public void shouldHandleTooLargeStartPointInSubstring() + { + // Given + UTF8StringValue value = utf8Value( "hello".getBytes( UTF_8 ) ); + + // When + TextValue substring = value.substring( 8, 5 ); + + // Then + assertThat( substring, equalTo( StringValue.EMTPY ) ); + } + + @Test + public void shouldHandleTooLargeLengthInSubstring() + { + // Given + UTF8StringValue value = utf8Value( "hello".getBytes( UTF_8 ) ); + + // When + TextValue substring = value.substring( 3, 76 ); + + // Then + assertThat( substring.stringValue(), equalTo( "lo" ) ); + } + + @Test + public void shouldThrowOnNegativeStart() + { + // Given + UTF8StringValue value = utf8Value( "hello".getBytes( UTF_8 ) ); + + // Expect + exception.expect( IndexOutOfBoundsException.class ); + + // When + value.substring( -4, 3 ); + } + + @Test + public void shouldThrowOnNegativeLength() + { + // Given + UTF8StringValue value = utf8Value( "hello".getBytes( UTF_8 ) ); + + // Expect + exception.expect( IndexOutOfBoundsException.class ); + + // When + value.substring( 4, -3 ); + } }