Fix JsonValueReader to support up to 255 levels of nesting. (#417)

Follow up to https://github.com/square/moshi/pull/349
This commit is contained in:
Jesse Wilson 2018-01-07 14:55:02 -05:00 committed by GitHub
parent d2ef4b5a61
commit 359244e996
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 40 additions and 32 deletions

View file

@ -49,7 +49,7 @@ final class JsonValueReader extends JsonReader {
/** Sentinel object pushed on {@link #stack} when the reader is closed. */
private static final Object JSON_READER_CLOSED = new Object();
private final Object[] stack = new Object[32];
private Object[] stack = new Object[32];
JsonValueReader(Object root) {
scopes[stackSize] = JsonScope.NONEMPTY_DOCUMENT;
@ -308,7 +308,13 @@ final class JsonValueReader extends JsonReader {
private void push(Object newTop) {
if (stackSize == stack.length) {
throw new JsonDataException("Nesting too deep at " + getPath());
if (stackSize == 256) {
throw new JsonDataException("Nesting too deep at " + getPath());
}
scopes = Arrays.copyOf(scopes, scopes.length * 2);
pathNames = Arrays.copyOf(pathNames, pathNames.length * 2);
pathIndices = Arrays.copyOf(pathIndices, pathIndices.length * 2);
stack = Arrays.copyOf(stack, stack.length * 2);
}
stack[stackSize++] = newTop;
}

View file

@ -33,6 +33,7 @@ import static com.squareup.moshi.JsonReader.Token.NAME;
import static com.squareup.moshi.JsonReader.Token.NULL;
import static com.squareup.moshi.JsonReader.Token.NUMBER;
import static com.squareup.moshi.JsonReader.Token.STRING;
import static com.squareup.moshi.TestUtil.MAX_DEPTH;
import static com.squareup.moshi.TestUtil.newReader;
import static com.squareup.moshi.TestUtil.repeat;
import static org.assertj.core.api.Assertions.assertThat;
@ -1023,28 +1024,28 @@ public final class JsonUtf8ReaderTest {
}
@Test public void tooDeeplyNestedArrays() throws IOException {
JsonReader reader = newReader(repeat("[", 256) + repeat("]", 256));
for (int i = 0; i < 255; i++) {
JsonReader reader = newReader(repeat("[", MAX_DEPTH + 1) + repeat("]", MAX_DEPTH + 1));
for (int i = 0; i < MAX_DEPTH; i++) {
reader.beginArray();
}
try {
reader.beginArray();
fail();
} catch (JsonDataException expected) {
assertThat(expected).hasMessage("Nesting too deep at $" + repeat("[0]", 255));
assertThat(expected).hasMessage("Nesting too deep at $" + repeat("[0]", MAX_DEPTH));
}
}
@Test public void tooDeeplyNestedObjects() throws IOException {
// Build a JSON document structured like {"a":{"a":{"a":{"a":true}}}}, but 31 levels deep.
// Build a JSON document structured like {"a":{"a":{"a":{"a":true}}}}, but 255 levels deep.
String array = "{\"a\":%s}";
String json = "true";
for (int i = 0; i < 256; i++) {
for (int i = 0; i < MAX_DEPTH + 1; i++) {
json = String.format(array, json);
}
JsonReader reader = newReader(json);
for (int i = 0; i < 255; i++) {
for (int i = 0; i < MAX_DEPTH; i++) {
reader.beginObject();
assertThat(reader.nextName()).isEqualTo("a");
}
@ -1052,7 +1053,7 @@ public final class JsonUtf8ReaderTest {
reader.beginObject();
fail();
} catch (JsonDataException expected) {
assertThat(expected).hasMessage("Nesting too deep at $" + repeat(".a", 255));
assertThat(expected).hasMessage("Nesting too deep at $" + repeat(".a", MAX_DEPTH));
}
}

View file

@ -24,6 +24,8 @@ import java.util.List;
import java.util.Map;
import org.junit.Test;
import static com.squareup.moshi.TestUtil.MAX_DEPTH;
import static com.squareup.moshi.TestUtil.repeat;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.assertj.core.api.Assertions.assertThat;
@ -439,29 +441,28 @@ public final class JsonValueReaderTest {
@Test public void tooDeeplyNestedArrays() throws IOException {
Object root = Collections.emptyList();
for (int i = 0; i < 32; i++) {
for (int i = 0; i < MAX_DEPTH + 1; i++) {
root = singletonList(root);
}
JsonReader reader = new JsonValueReader(root);
for (int i = 0; i < 31; i++) {
for (int i = 0; i < MAX_DEPTH; i++) {
reader.beginArray();
}
try {
reader.beginArray();
fail();
} catch (JsonDataException expected) {
assertThat(expected).hasMessage("Nesting too deep at $[0][0][0][0][0][0][0][0][0][0][0][0][0]"
+ "[0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0]");
assertThat(expected).hasMessage("Nesting too deep at $" + repeat("[0]", MAX_DEPTH + 1));
}
}
@Test public void tooDeeplyNestedObjects() throws IOException {
Object root = Boolean.TRUE;
for (int i = 0; i < 32; i++) {
for (int i = 0; i < MAX_DEPTH + 1; i++) {
root = singletonMap("a", root);
}
JsonReader reader = new JsonValueReader(root);
for (int i = 0; i < 31; i++) {
for (int i = 0; i < MAX_DEPTH; i++) {
reader.beginObject();
assertThat(reader.nextName()).isEqualTo("a");
}
@ -469,8 +470,7 @@ public final class JsonValueReaderTest {
reader.beginObject();
fail();
} catch (JsonDataException expected) {
assertThat(expected).hasMessage(
"Nesting too deep at $.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.");
assertThat(expected).hasMessage("Nesting too deep at $" + repeat(".a", MAX_DEPTH) + ".");
}
}
}

View file

@ -25,6 +25,7 @@ import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
import static com.squareup.moshi.TestUtil.MAX_DEPTH;
import static com.squareup.moshi.TestUtil.repeat;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
@ -423,19 +424,19 @@ public final class JsonWriterTest {
@Test public void deepNestingArrays() throws IOException {
JsonWriter writer = factory.newWriter();
for (int i = 0; i < 31; i++) {
for (int i = 0; i < MAX_DEPTH; i++) {
writer.beginArray();
}
for (int i = 0; i < 31; i++) {
for (int i = 0; i < MAX_DEPTH; i++) {
writer.endArray();
}
assertThat(factory.json())
.isEqualTo("[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]");
.isEqualTo(repeat("[", MAX_DEPTH) + repeat("]", MAX_DEPTH));
}
@Test public void tooDeepNestingArrays() throws IOException {
@Test public void tooDeeplyNestingArrays() throws IOException {
JsonWriter writer = factory.newWriter();
for (int i = 0; i < 255; i++) {
for (int i = 0; i < MAX_DEPTH; i++) {
writer.beginArray();
}
try {
@ -443,29 +444,27 @@ public final class JsonWriterTest {
fail();
} catch (JsonDataException expected) {
assertThat(expected).hasMessage("Nesting too deep at $"
+ repeat("[0]", 255) + ": circular reference?");
+ repeat("[0]", MAX_DEPTH) + ": circular reference?");
}
}
@Test public void deepNestingObjects() throws IOException {
JsonWriter writer = factory.newWriter();
for (int i = 0; i < 31; i++) {
for (int i = 0; i < MAX_DEPTH; i++) {
writer.beginObject();
writer.name("a");
}
writer.value(true);
for (int i = 0; i < 31; i++) {
for (int i = 0; i < MAX_DEPTH; i++) {
writer.endObject();
}
assertThat(factory.json()).isEqualTo(""
+ "{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":"
+ "{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":"
+ "{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":true}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}");
assertThat(factory.json()).isEqualTo(
repeat("{\"a\":", MAX_DEPTH) + "true" + repeat("}", MAX_DEPTH));
}
@Test public void tooDeepNestingObjects() throws IOException {
@Test public void tooDeeplyNestingObjects() throws IOException {
JsonWriter writer = factory.newWriter();
for (int i = 0; i < 255; i++) {
for (int i = 0; i < MAX_DEPTH; i++) {
writer.beginObject();
writer.name("a");
}
@ -474,7 +473,7 @@ public final class JsonWriterTest {
fail();
} catch (JsonDataException expected) {
assertThat(expected).hasMessage("Nesting too deep at $"
+ repeat(".a", 255) + ": circular reference?");
+ repeat(".a", MAX_DEPTH) + ": circular reference?");
}
}

View file

@ -19,6 +19,8 @@ import java.util.Arrays;
import okio.Buffer;
final class TestUtil {
static final int MAX_DEPTH = 255;
static JsonReader newReader(String json) {
Buffer buffer = new Buffer().writeUtf8(json);
return JsonReader.of(buffer);