Skip to content

Commit 5e8656a

Browse files
committed
Throw an exception if Writeable.Reader reads null
If a Writeable.Reader returns null it is always a bug, probably one that will cause corruption in the StreamInput it was trying to read from. This commit adds a check that attempts to catch these errors quickly including the name of the reader.
1 parent 6dd164d commit 5e8656a

File tree

4 files changed

+38
-4
lines changed

4 files changed

+38
-4
lines changed

core/src/main/java/org/elasticsearch/common/io/stream/NamedWriteableAwareStreamInput.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ public NamedWriteableAwareStreamInput(StreamInput delegate, NamedWriteableRegist
3636
@Override
3737
<C> C readNamedWriteable(Class<C> categoryClass) throws IOException {
3838
String name = readString();
39-
return namedWriteableRegistry.getReader(categoryClass, name).read(this);
39+
Writeable.Reader<? extends C> reader = namedWriteableRegistry.getReader(categoryClass, name);
40+
C c = reader.read(this);
41+
if (c == null) {
42+
throw new IOException(
43+
"Writeable.Reader [" + reader + "] returned null which is not allowed and probably means it screwed up the stream.");
44+
}
45+
return c;
4046
}
4147
}

core/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -566,9 +566,14 @@ public <T extends Streamable> T readOptionalStreamable(Supplier<T> supplier) thr
566566
}
567567
}
568568

569-
public <T extends Writeable> T readOptionalWriteable(Writeable.Reader<T> provider) throws IOException {
569+
public <T extends Writeable> T readOptionalWriteable(Writeable.Reader<T> reader) throws IOException {
570570
if (readBoolean()) {
571-
return provider.read(this);
571+
T t = reader.read(this);
572+
if (t == null) {
573+
throw new IOException("Writeable.Reader [" + reader
574+
+ "] returned null which is not allowed and probably means it screwed up the stream.");
575+
}
576+
return t;
572577
} else {
573578
return null;
574579
}

core/src/main/java/org/elasticsearch/common/io/stream/Writeable.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ default T readFrom(StreamInput in) throws IOException {
5151

5252
/**
5353
* Reference to a method that can read some object from a stream. By convention this is a constructor that takes
54-
* {@linkplain StreamInput} as an argument for most classes and a static method for things like enums.
54+
* {@linkplain StreamInput} as an argument for most classes and a static method for things like enums. Returning null from one of these
55+
* is always wrong - for that we use methods like {@link StreamInput#readOptionalWriteable(Reader)}.
5556
*/
5657
@FunctionalInterface
5758
interface Reader<R> {

core/src/test/java/org/elasticsearch/common/io/stream/BytesStreamsTests.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.Objects;
3030

3131
import static org.hamcrest.Matchers.closeTo;
32+
import static org.hamcrest.Matchers.endsWith;
3233
import static org.hamcrest.Matchers.equalTo;
3334
import static org.hamcrest.Matchers.is;
3435
import static org.hamcrest.Matchers.startsWith;
@@ -373,6 +374,27 @@ public void testNamedWriteableNotSupportedWithoutWrapping() throws IOException {
373374
}
374375
}
375376

377+
public void testNamedWriteableReaderReturnsNull() throws IOException {
378+
BytesStreamOutput out = new BytesStreamOutput();
379+
NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry();
380+
namedWriteableRegistry.register(BaseNamedWriteable.class, TestNamedWriteable.NAME, (StreamInput in) -> null);
381+
TestNamedWriteable namedWriteableIn = new TestNamedWriteable(randomAsciiOfLengthBetween(1, 10), randomAsciiOfLengthBetween(1, 10));
382+
out.writeNamedWriteable(namedWriteableIn);
383+
byte[] bytes = out.bytes().toBytes();
384+
StreamInput in = new NamedWriteableAwareStreamInput(StreamInput.wrap(bytes), namedWriteableRegistry);
385+
assertEquals(in.available(), bytes.length);
386+
IOException e = expectThrows(IOException.class, () -> in.readNamedWriteable(BaseNamedWriteable.class));
387+
assertThat(e.getMessage(), endsWith("] returned null which is not allowed and probably means it screwed up the stream."));
388+
}
389+
390+
public void testOptionalWriteableReaderReturnsNull() throws IOException {
391+
BytesStreamOutput out = new BytesStreamOutput();
392+
out.writeOptionalWriteable(new TestNamedWriteable(randomAsciiOfLengthBetween(1, 10), randomAsciiOfLengthBetween(1, 10)));
393+
StreamInput in = StreamInput.wrap(out.bytes().toBytes());
394+
IOException e = expectThrows(IOException.class, () -> in.readOptionalWriteable((StreamInput ignored) -> null));
395+
assertThat(e.getMessage(), endsWith("] returned null which is not allowed and probably means it screwed up the stream."));
396+
}
397+
376398
private static abstract class BaseNamedWriteable<T> implements NamedWriteable<T> {
377399

378400
}

0 commit comments

Comments
 (0)