Repository: netty/netty
Issue: AppendableCharSequence.subSequence throws instead of returning empty subsequence
Labels: defect

I am writing message decoder using as a base approach of `HttpRequestDecoder` (`ByteBuf -> AppendableCharSequence`) and encountered following problem:
`AppendableCharSequence.subSequence(int start, int end);` violates the `CharSequence` interface contract when `start == end`.

### Expected behavior
Return an empty sequence, as specified in `CharSequence.subSequence` doc:
> _if <tt>start == end</tt> then an empty sequence is returned._

### Actual behavior
`IllegalArgumentException: length: 0 (length: >= 1)`

### Steps to reproduce

### Minimal yet complete reproducer code (or URL to code)
```
		AppendableCharSequence asc = new AppendableCharSequence(128);
		asc.append('/');
		Matcher m = Pattern.compile("/([^ ]*+)").matcher(asc);
		if (m.matches()) m.group(1);
```
or https://gist.github.com/aimozg/075074c6e6362f800bb1fff21bf6a047

### Netty version
4.124.Final

### Java version
1.8

Issue comments:
@aimozg sounds like a bug... Can you submit a PR with a test and fix ?
@normanmaurer I'd rather someone more experienced with netty internals do it, really.
I'm unsure how to fix it - 
1) Just return an min-size empty ASC?
2) Allow 0-size in constructors? (Is that used somewhere)
3) Something else?

quick-and-dirty 1) would be 
```diff
    @Override
    public AppendableCharSequence subSequence(int start, int end) {
+       if (start == end) {
+           return new AppendableCharSequence(1);
+       }
        return new AppendableCharSequence(Arrays.copyOfRange(chars, start, end));
    }
```
@aimozg please check https://github.com/netty/netty/pull/8798 ... That said you should not use this class as its in the "internal" package and so may be removed / changed etc without notice.

Pull request review comments:

Patch that resolved the issue:
diff --git a/common/src/main/java/io/netty/util/internal/AppendableCharSequence.java b/common/src/main/java/io/netty/util/internal/AppendableCharSequence.java
index 408c32f3802..e8e6abf5661 100644
--- a/common/src/main/java/io/netty/util/internal/AppendableCharSequence.java
+++ b/common/src/main/java/io/netty/util/internal/AppendableCharSequence.java
@@ -63,6 +63,12 @@ public char charAtUnsafe(int index) {
 
     @Override
     public AppendableCharSequence subSequence(int start, int end) {
+        if (start == end) {
+            // If start and end index is the same we need to return an empty sequence to conform to the interface.
+            // As our expanding logic depends on the fact that we have a char[] with length > 0 we need to construct
+            // an instance for which this is true.
+            return new AppendableCharSequence(Math.min(16, chars.length));
+        }
         return new AppendableCharSequence(Arrays.copyOfRange(chars, start, end));
     }
 


Test patch:
diff --git a/common/src/test/java/io/netty/util/internal/AppendableCharSequenceTest.java b/common/src/test/java/io/netty/util/internal/AppendableCharSequenceTest.java
index 2d7bab4ec84..9d08c3ee577 100644
--- a/common/src/test/java/io/netty/util/internal/AppendableCharSequenceTest.java
+++ b/common/src/test/java/io/netty/util/internal/AppendableCharSequenceTest.java
@@ -64,6 +64,16 @@ public void testSubSequence() {
         assertEquals("abcdefghij", master.subSequence(0, 10).toString());
     }
 
+    @Test
+    public void testEmptySubSequence() {
+        AppendableCharSequence master = new AppendableCharSequence(26);
+        master.append("abcdefghijlkmonpqrstuvwxyz");
+        AppendableCharSequence sub =  master.subSequence(0, 0);
+        assertEquals(0, sub.length());
+        sub.append('b');
+        assertEquals('b', sub.charAt(0));
+    }
+
     private static void testSimpleAppend0(AppendableCharSequence seq) {
         String text = "testdata";
         for (int i = 0; i < text.length(); i++) {