You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@servicecomb.apache.org by li...@apache.org on 2018/10/23 08:43:16 UTC
[incubator-servicecomb-java-chassis] 02/02: [SCB-965] to avoid copy
too many code, use javassist to fix the DoS problem
This is an automated email from the ASF dual-hosted git repository.
liubao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-servicecomb-java-chassis.git
commit e17c9fd91792ea068341363205fca4d7b4ae5b43
Author: wujimin <wu...@huawei.com>
AuthorDate: Thu Oct 18 12:13:02 2018 +0800
[SCB-965] to avoid copy too many code, use javassist to fix the DoS problem
---
.../com/fasterxml/jackson/core/base/DoSFix.java | 151 +++++++++++++
.../jackson/core/base/DoSParserFixed.java | 76 +++++++
.../rest/codec/AbstractRestObjectMapper.java | 5 +
.../common/rest/codec/RestObjectMapper.java | 7 +
.../codec/produce/ProduceProcessorManager.java | 4 +-
.../common/rest/codec/fix/TestDoSFix.java | 246 +++++++++++++++++++++
6 files changed, 488 insertions(+), 1 deletion(-)
diff --git a/common/common-rest/src/main/java/com/fasterxml/jackson/core/base/DoSFix.java b/common/common-rest/src/main/java/com/fasterxml/jackson/core/base/DoSFix.java
new file mode 100644
index 0000000..6c46eca
--- /dev/null
+++ b/common/common-rest/src/main/java/com/fasterxml/jackson/core/base/DoSFix.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.fasterxml.jackson.core.base;
+
+import org.apache.servicecomb.foundation.common.utils.JvmUtils;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.json.ByteSourceJsonBootstrapper;
+import com.fasterxml.jackson.core.json.ReaderBasedJsonParser;
+import com.fasterxml.jackson.core.json.UTF8StreamJsonParser;
+import com.fasterxml.jackson.databind.MappingJsonFactory;
+import com.netflix.config.DynamicPropertyFactory;
+
+import javassist.CannotCompileException;
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.CtMethod;
+import javassist.LoaderClassPath;
+import javassist.NotFoundException;
+
+/**
+ * will be deleted after jackson fix the DoS problem:
+ * https://github.com/FasterXML/jackson-databind/issues/2157
+ */
+public class DoSFix {
+ private static final String SUFFIX = "Fixed";
+
+ private static boolean enabled = DynamicPropertyFactory.getInstance()
+ .getBooleanProperty("servicecomb.jackson.fix.DoS.enabled", true).get();
+
+ private static boolean fixed;
+
+ private static Class<?> mappingJsonFactoryClass;
+
+ public static synchronized void init() {
+ if (fixed || !enabled) {
+ return;
+ }
+
+ fix();
+ }
+
+ public static JsonFactory createJsonFactory() {
+ try {
+ return (JsonFactory) mappingJsonFactoryClass.newInstance();
+ } catch (Throwable e) {
+ throw new IllegalStateException("Failed to create JsonFactory.", e);
+ }
+ }
+
+ private static void fix() {
+ try {
+ ClassLoader classLoader = JvmUtils.correctClassLoader(DoSFix.class.getClassLoader());
+ ClassPool pool = new ClassPool(ClassPool.getDefault());
+ pool.appendClassPath(new LoaderClassPath(classLoader));
+
+ fixParserBase(classLoader, pool);
+ fixReaderParser(classLoader, pool);
+ fixStreamParser(classLoader, pool);
+ fixByteSourceJsonBootstrapper(classLoader, pool);
+
+ CtClass ctJsonFactoryFixedClass = fixJsonFactory(classLoader, pool);
+ fixMappingJsonFactoryClass(classLoader, pool, ctJsonFactoryFixedClass);
+
+ fixed = true;
+ } catch (Throwable e) {
+ throw new IllegalStateException(
+ "Failed to fix jackson DoS bug.",
+ e);
+ }
+ }
+
+ private static void fixMappingJsonFactoryClass(ClassLoader classLoader, ClassPool pool,
+ CtClass ctJsonFactoryFixedClass) throws NotFoundException, CannotCompileException {
+ CtClass ctMappingJsonFactoryClass = pool
+ .getAndRename(MappingJsonFactory.class.getName(), MappingJsonFactory.class.getName() + SUFFIX);
+ ctMappingJsonFactoryClass.setSuperclass(ctJsonFactoryFixedClass);
+ mappingJsonFactoryClass = ctMappingJsonFactoryClass.toClass(classLoader, null);
+ }
+
+ private static CtClass fixJsonFactory(ClassLoader classLoader, ClassPool pool)
+ throws NotFoundException, CannotCompileException {
+ CtClass ctJsonFactoryClass = pool.getCtClass(JsonFactory.class.getName());
+ CtClass ctJsonFactoryFixedClass = pool.makeClass(JsonFactory.class.getName() + SUFFIX);
+ ctJsonFactoryFixedClass.setSuperclass(ctJsonFactoryClass);
+ for (CtMethod ctMethod : ctJsonFactoryClass.getDeclaredMethods()) {
+ if (ctMethod.getName().equals("_createParser")) {
+ ctJsonFactoryFixedClass.addMethod(new CtMethod(ctMethod, ctJsonFactoryFixedClass, null));
+ }
+ }
+ ctJsonFactoryFixedClass
+ .replaceClassName(ReaderBasedJsonParser.class.getName(), ReaderBasedJsonParser.class.getName() + SUFFIX);
+ ctJsonFactoryFixedClass
+ .replaceClassName(UTF8StreamJsonParser.class.getName(), UTF8StreamJsonParser.class.getName() + SUFFIX);
+ ctJsonFactoryFixedClass.replaceClassName(ByteSourceJsonBootstrapper.class.getName(),
+ ByteSourceJsonBootstrapper.class.getName() + SUFFIX);
+ ctJsonFactoryFixedClass.toClass(classLoader, null);
+
+ return ctJsonFactoryFixedClass;
+ }
+
+ private static void fixByteSourceJsonBootstrapper(ClassLoader classLoader, ClassPool pool)
+ throws NotFoundException, CannotCompileException {
+ CtClass ctByteSourceJsonBootstrapper = pool
+ .getAndRename(ByteSourceJsonBootstrapper.class.getName(), ByteSourceJsonBootstrapper.class.getName() + SUFFIX);
+ ctByteSourceJsonBootstrapper
+ .replaceClassName(UTF8StreamJsonParser.class.getName(), UTF8StreamJsonParser.class.getName() + SUFFIX);
+ ctByteSourceJsonBootstrapper
+ .replaceClassName(ReaderBasedJsonParser.class.getName(), ReaderBasedJsonParser.class.getName() + SUFFIX);
+ ctByteSourceJsonBootstrapper.toClass(classLoader, null);
+ }
+
+ private static void fixStreamParser(ClassLoader classLoader, ClassPool pool)
+ throws NotFoundException, CannotCompileException {
+ CtClass ctStreamClass = pool
+ .getAndRename(UTF8StreamJsonParser.class.getName(), UTF8StreamJsonParser.class.getName() + SUFFIX);
+ ctStreamClass.replaceClassName(ParserBase.class.getName(), ParserBase.class.getName() + SUFFIX);
+ ctStreamClass.toClass(classLoader, null);
+ }
+
+ private static void fixReaderParser(ClassLoader classLoader, ClassPool pool)
+ throws NotFoundException, CannotCompileException {
+ CtClass ctReaderClass = pool
+ .getAndRename(ReaderBasedJsonParser.class.getName(), ReaderBasedJsonParser.class.getName() + SUFFIX);
+ ctReaderClass.replaceClassName(ParserBase.class.getName(), ParserBase.class.getName() + SUFFIX);
+ ctReaderClass.toClass(classLoader, null);
+ }
+
+ private static void fixParserBase(ClassLoader classLoader, ClassPool pool)
+ throws NotFoundException, CannotCompileException {
+ CtMethod ctMethodFixed = pool.get(DoSParserFixed.class.getName()).getDeclaredMethod("_parseSlowInt");
+ CtClass baseClass = pool.getAndRename(ParserBase.class.getName(), ParserBase.class.getName() + SUFFIX);
+ baseClass.removeMethod(baseClass.getDeclaredMethod("_parseSlowInt"));
+ baseClass.addMethod(new CtMethod(ctMethodFixed, baseClass, null));
+ baseClass.toClass(classLoader, null);
+ }
+}
diff --git a/common/common-rest/src/main/java/com/fasterxml/jackson/core/base/DoSParserFixed.java b/common/common-rest/src/main/java/com/fasterxml/jackson/core/base/DoSParserFixed.java
new file mode 100644
index 0000000..71fbdc6
--- /dev/null
+++ b/common/common-rest/src/main/java/com/fasterxml/jackson/core/base/DoSParserFixed.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.fasterxml.jackson.core.base;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.math.BigInteger;
+
+import com.fasterxml.jackson.core.ObjectCodec;
+import com.fasterxml.jackson.core.io.IOContext;
+import com.fasterxml.jackson.core.io.NumberInput;
+import com.fasterxml.jackson.core.json.ReaderBasedJsonParser;
+import com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer;
+
+/**
+ * will not be use directly
+ * just get _parseSlowInt/_parseSlowFloat bytecode and replace to ParserBase
+ */
+public abstract class DoSParserFixed extends ReaderBasedJsonParser {
+ public DoSParserFixed(IOContext ctxt, int features, Reader r,
+ ObjectCodec codec, CharsToNameCanonicalizer st,
+ char[] inputBuffer, int start, int end, boolean bufferRecyclable) {
+ super(ctxt, features, r, codec, st, inputBuffer, start, end, bufferRecyclable);
+ }
+
+ private void _parseSlowInt(int expType) throws IOException {
+ String numStr = _textBuffer.contentsAsString();
+ try {
+ int len = _intLength;
+ char[] buf = _textBuffer.getTextBuffer();
+ int offset = _textBuffer.getTextOffset();
+ if (_numberNegative) {
+ ++offset;
+ }
+ // Some long cases still...
+ if (NumberInput.inLongRange(buf, offset, len, _numberNegative)) {
+ // Probably faster to construct a String, call parse, than to use BigInteger
+ _numberLong = Long.parseLong(numStr);
+ _numTypesValid = NR_LONG;
+ } else {
+ // nope, need the heavy guns... (rare case)
+
+ // *** fix DoS attack begin ***
+ if (NR_DOUBLE == expType || NR_FLOAT == expType) {
+ _numberDouble = Double.parseDouble(numStr);
+ _numTypesValid = NR_DOUBLE;
+ return;
+ }
+ if (NR_BIGINT != expType) {
+ throw new NumberFormatException("invalid numeric value '" + numStr + "'");
+ }
+ // *** fix DoS attack end ***
+
+ _numberBigInt = new BigInteger(numStr);
+ _numTypesValid = NR_BIGINT;
+ }
+ } catch (NumberFormatException nex) {
+ // Can this ever occur? Due to overflow, maybe?
+ _wrapError("Malformed numeric value '" + numStr + "'", nex);
+ }
+ }
+}
diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/AbstractRestObjectMapper.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/AbstractRestObjectMapper.java
index 0ca5fa6..1f64d3d 100644
--- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/AbstractRestObjectMapper.java
+++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/AbstractRestObjectMapper.java
@@ -17,10 +17,15 @@
package org.apache.servicecomb.common.rest.codec;
+import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
public abstract class AbstractRestObjectMapper extends ObjectMapper {
private static final long serialVersionUID = 189026839992490564L;
+ public AbstractRestObjectMapper(JsonFactory jsonFactory) {
+ super(jsonFactory);
+ }
+
abstract public String convertToString(Object value) throws Exception;
}
diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/RestObjectMapper.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/RestObjectMapper.java
index 8f20acb..ae1a45f 100644
--- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/RestObjectMapper.java
+++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/RestObjectMapper.java
@@ -23,6 +23,7 @@ import java.util.Date;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser.Feature;
+import com.fasterxml.jackson.core.base.DoSFix;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonSerializer;
@@ -34,6 +35,10 @@ import com.fasterxml.jackson.databind.type.TypeFactory;
import io.vertx.core.json.JsonObject;
public class RestObjectMapper extends AbstractRestObjectMapper {
+ static {
+ DoSFix.init();
+ }
+
private static class JsonObjectSerializer extends JsonSerializer<JsonObject> {
@Override
public void serialize(JsonObject value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
@@ -47,6 +52,8 @@ public class RestObjectMapper extends AbstractRestObjectMapper {
@SuppressWarnings("deprecation")
public RestObjectMapper() {
+ super(DoSFix.createJsonFactory());
+
// swagger中要求date使用ISO8601格式传递,这里与之做了功能绑定,这在cse中是没有问题的
setDateFormat(new com.fasterxml.jackson.databind.util.ISO8601DateFormat() {
private static final long serialVersionUID = 7798938088541203312L;
diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/produce/ProduceProcessorManager.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/produce/ProduceProcessorManager.java
index 255b35d..6bd5bab 100644
--- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/produce/ProduceProcessorManager.java
+++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/produce/ProduceProcessorManager.java
@@ -20,6 +20,7 @@ package org.apache.servicecomb.common.rest.codec.produce;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+
import javax.ws.rs.core.MediaType;
import org.apache.servicecomb.foundation.common.RegisterManager;
@@ -47,8 +48,9 @@ public final class ProduceProcessorManager extends RegisterManager<String, Produ
super(NAME);
Set<String> set = new HashSet<>();
produceProcessor.forEach(processor -> {
- if (set.add(processor.getName()))
+ if (set.add(processor.getName())) {
register(processor.getName(), processor);
+ }
});
}
}
diff --git a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/codec/fix/TestDoSFix.java b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/codec/fix/TestDoSFix.java
new file mode 100644
index 0000000..baff897
--- /dev/null
+++ b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/codec/fix/TestDoSFix.java
@@ -0,0 +1,246 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.servicecomb.common.rest.codec.fix;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.util.concurrent.Callable;
+
+import org.apache.servicecomb.common.rest.codec.RestObjectMapper;
+import org.apache.servicecomb.foundation.test.scaffolding.model.Color;
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.exc.InvalidFormatException;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
+import com.google.common.base.Strings;
+
+public class TestDoSFix {
+ static ObjectMapper mapper = new RestObjectMapper();
+
+ static String invalidNum = Strings.repeat("9", 100_0000);
+
+ static String invalidStr = "\"" + invalidNum + "\"";
+
+ static String invalidArrNum = "[" + invalidNum + "]";
+
+ static String invalidArrStr = "[\"" + invalidNum + "\"]";
+
+ public static class Model {
+ public Color color;
+
+ public char cValue;
+
+ public Character cObjValue;
+
+ public byte bValue;
+
+ public Byte bObjValue;
+
+ public short sValue;
+
+ public Short sObjValue;
+
+ public int iValue;
+
+ public Integer iObjValue;
+
+ public long lValue;
+
+ public Long lObjValue;
+
+ public float fValue;
+
+ public Float fObjValue;
+
+ public double dValue;
+
+ public Double dObjValue;
+ }
+
+ void fastFail(Callable<?> callable, Class<?> eCls) {
+ long start = System.currentTimeMillis();
+ try {
+ Object ret = callable.call();
+ Assert.fail("expect failed, but succes to be " + ret);
+ } catch (AssertionError e) {
+ throw e;
+ } catch (Throwable e) {
+ if (eCls != e.getClass()) {
+ e.printStackTrace();
+ }
+ Assert.assertEquals(eCls, e.getClass());
+ }
+
+ long time = System.currentTimeMillis() - start;
+ Assert.assertTrue("did not fix DoS problem, time:" + time, time < 1000);
+ }
+
+ void fastFail(String input, Class<?> cls, Class<?> eCls) {
+ fastFail(() -> mapper.readValue(input, cls), eCls);
+
+ fastFail(() -> mapper.readValue(new ByteArrayInputStream(input.getBytes()), cls), eCls);
+ }
+
+ void batFastFail(Class cls, Class<?> e1, Class<?> e2) {
+ fastFail(invalidNum, cls, e1);
+ fastFail(invalidStr, cls, e2);
+ fastFail(invalidArrNum, cls, e1);
+ fastFail(invalidArrStr, cls, e2);
+ }
+
+ void batFastFail(Class cls) {
+ batFastFail(cls, JsonParseException.class, InvalidFormatException.class);
+ }
+
+ void batFastFail(String fieldName, Class<?> e1, Class<?> e2) {
+ fastFail("{\"" + fieldName + "\":" + invalidNum + "}", Model.class, e1);
+ fastFail("{\"" + fieldName + "\":\"" + invalidNum + "\"}", Model.class, e2);
+ fastFail("{\"" + fieldName + "\":[" + invalidNum + "]}", Model.class, e1);
+ fastFail("{\"" + fieldName + "\":[\"" + invalidNum + "\"]}", Model.class, e2);
+ }
+
+ void batFastFail(String fieldName) {
+ batFastFail(fieldName, JsonMappingException.class, InvalidFormatException.class);
+ }
+
+ @Test
+ public void testEnum() {
+ batFastFail(Color.class);
+ batFastFail("color");
+ }
+
+ @Test
+ public void testChar() {
+ batFastFail(char.class, JsonParseException.class, MismatchedInputException.class);
+ batFastFail(Character.class, JsonParseException.class, MismatchedInputException.class);
+
+ batFastFail("cValue", JsonMappingException.class, MismatchedInputException.class);
+ batFastFail("cObjValue", JsonMappingException.class, MismatchedInputException.class);
+ }
+
+ @Test
+ public void testByte() {
+ batFastFail(byte.class);
+ batFastFail(Byte.class);
+
+ batFastFail("bValue");
+ batFastFail("bObjValue");
+ }
+
+ @Test
+ public void testShort() {
+ batFastFail(short.class);
+ batFastFail(Short.class);
+
+ batFastFail("sValue");
+ batFastFail("sObjValue");
+ }
+
+ @Test
+ public void testInt() {
+ batFastFail(int.class);
+ batFastFail(Integer.class);
+
+ batFastFail("iValue");
+ batFastFail("iObjValue");
+ }
+
+ @Test
+ public void testLong() {
+ batFastFail(long.class);
+ batFastFail(Long.class);
+
+ batFastFail("lValue");
+ batFastFail("lObjValue");
+ }
+
+ Object fastSucc(Callable<?> callable) {
+ long start = System.currentTimeMillis();
+ try {
+ Object ret = callable.call();
+ Assert.assertTrue(System.currentTimeMillis() - start < 1000);
+ return ret;
+ } catch (Throwable e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ Object fastSucc(String input, Class<?> cls) {
+ return fastSucc(() -> mapper.readValue(input, cls));
+ }
+
+ Object fastSucc(InputStream input, Class<?> cls) {
+ return fastSucc(() -> {
+ input.reset();
+ return mapper.readValue(input, cls);
+ });
+ }
+
+ void batFastSucc(Class cls, Object expected) {
+ Assert.assertEquals(expected, fastSucc(invalidNum, cls));
+ Assert.assertEquals(expected, fastSucc(new ByteArrayInputStream(invalidNum.getBytes()), cls));
+
+ Assert.assertEquals(expected, fastSucc(invalidStr, cls));
+ Assert.assertEquals(expected, fastSucc(new ByteArrayInputStream(invalidStr.getBytes()), cls));
+
+ Assert.assertEquals(expected, fastSucc(invalidArrNum, cls));
+ Assert.assertEquals(expected, fastSucc(new ByteArrayInputStream(invalidArrNum.getBytes()), cls));
+
+ Assert.assertEquals(expected, fastSucc(invalidArrStr, cls));
+ Assert.assertEquals(expected, fastSucc(new ByteArrayInputStream(invalidArrStr.getBytes()), cls));
+ }
+
+ void checkField(Model model, String fieldName, Object expected) {
+ try {
+ Field field = Model.class.getField(fieldName);
+ Object value = field.get(model);
+ Assert.assertEquals(expected, value);
+ } catch (Throwable e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ void batFastSucc(String fieldName, Object expected) {
+ checkField((Model) fastSucc("{\"" + fieldName + "\":" + invalidNum + "}", Model.class), fieldName, expected);
+ checkField((Model) fastSucc("{\"" + fieldName + "\":\"" + invalidNum + "\"}", Model.class), fieldName, expected);
+ checkField((Model) fastSucc("{\"" + fieldName + "\":[" + invalidNum + "]}", Model.class), fieldName, expected);
+ checkField((Model) fastSucc("{\"" + fieldName + "\":[\"" + invalidNum + "\"]}", Model.class), fieldName, expected);
+ }
+
+ @Test
+ public void testFloat() {
+ batFastSucc(float.class, Float.POSITIVE_INFINITY);
+ batFastSucc(Float.class, Float.POSITIVE_INFINITY);
+
+ batFastSucc("fValue", Float.POSITIVE_INFINITY);
+ batFastSucc("fObjValue", Float.POSITIVE_INFINITY);
+ }
+
+ @Test
+ public void testDouble() {
+ batFastSucc(double.class, Double.POSITIVE_INFINITY);
+ batFastSucc(Double.class, Double.POSITIVE_INFINITY);
+
+ batFastSucc("dValue", Double.POSITIVE_INFINITY);
+ batFastSucc("dObjValue", Double.POSITIVE_INFINITY);
+ }
+}