Make RPC failures due to bad numerical values clearer (external issue 4263).

Review by: jat



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7238 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java
index cb5bf63..55274a6 100644
--- a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java
+++ b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java
@@ -457,12 +457,18 @@
   }
 
   public byte readByte() throws SerializationException {
-    return Byte.parseByte(extract());
+    String value = extract();
+    try {
+      return Byte.parseByte(value);
+    } catch (NumberFormatException e) {
+      throw getNumberFormatException(value, "byte",
+          Byte.MIN_VALUE, Byte.MAX_VALUE);
+    }
   }
 
   public char readChar() throws SerializationException {
     // just use an int, it's more foolproof
-    return (char) Integer.parseInt(extract());
+    return (char) readInt();
   }
 
   public double readDouble() throws SerializationException {
@@ -474,7 +480,13 @@
   }
 
   public int readInt() throws SerializationException {
-    return Integer.parseInt(extract());
+    String value = extract();
+    try {
+      return Integer.parseInt(value);
+    } catch (NumberFormatException e) {
+      throw getNumberFormatException(value, "int",
+          Integer.MIN_VALUE, Integer.MAX_VALUE);
+    }
   }
 
   public long readLong() throws SerializationException {
@@ -484,7 +496,13 @@
   }
 
   public short readShort() throws SerializationException {
-    return Short.parseShort(extract());
+    String value = extract();
+    try {
+      return Short.parseShort(value);
+    } catch (NumberFormatException e) {
+      throw getNumberFormatException(value, "short",
+          Short.MIN_VALUE, Short.MAX_VALUE);
+    }
   }
 
   public String readString() throws SerializationException {
@@ -771,6 +789,36 @@
       throw new SerializationException("Too few tokens in RPC request", e);
     }
   }
+  
+  /**
+   * Returns a suitable NumberFormatException with an explanatory message
+   * when a numerical value cannot be parsed according to its expected
+   * type.
+   *  
+   * @param value the value as read from the RPC stream
+   * @param type the name of the expected type
+   * @param minValue the smallest valid value for the expected type
+   * @param maxValue the largest valid value for the expected type
+   * @return a NumberFormatException with an explanatory message
+   */
+  private NumberFormatException getNumberFormatException(String value,
+      String type, double minValue, double maxValue) {
+    String message = "a non-numerical value";
+    try {
+      // Check the field contents in order to produce a more comprehensible
+      // error message
+      double d = Double.parseDouble(value);
+      if (d < minValue || d > maxValue) {
+        message = "an out-of-range value";
+      } else if (d != Math.floor(d)) {
+        message = "a fractional value";
+      }
+    } catch (NumberFormatException e2) {
+    }
+
+    return new NumberFormatException("Expected type '" + type + "' but received " +
+        message + ": " + value);
+  }
 
   /**
    * Returns a Map from a field name to the setter method for that field, for a
diff --git a/user/test/com/google/gwt/user/server/rpc/RPCTest.java b/user/test/com/google/gwt/user/server/rpc/RPCTest.java
index f59efd6..47a7b2b 100644
--- a/user/test/com/google/gwt/user/server/rpc/RPCTest.java
+++ b/user/test/com/google/gwt/user/server/rpc/RPCTest.java
@@ -18,6 +18,7 @@
 import static com.google.gwt.user.client.rpc.impl.AbstractSerializationStream.RPC_SEPARATOR_CHAR;
 
 import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
+import com.google.gwt.user.client.rpc.IsSerializable;
 import com.google.gwt.user.client.rpc.RemoteService;
 import com.google.gwt.user.client.rpc.SerializableException;
 import com.google.gwt.user.client.rpc.SerializationException;
@@ -68,6 +69,24 @@
     void method1();
   }
 
+  /**
+   * Test error message for an out=of-range int value.
+   * 
+   * @see RPCTest#testDecodeBadIntegerValue()
+   */
+  private static class Wrapper implements IsSerializable {
+    byte value1;
+    char value2;
+    short value3;
+    int value4;
+    public Wrapper() { }
+  }
+
+  @SuppressWarnings("rpc-validation")
+  private static interface WrapperIF extends RemoteService {
+    void method1(Wrapper w);
+  }
+
   private static final String VALID_ENCODED_REQUEST = ""
       + AbstractSerializationStream.SERIALIZATION_STREAM_VERSION
       + RPC_SEPARATOR_CHAR + // version
@@ -164,6 +183,90 @@
       "1\uffff" + // interface name
       "2\uffff" + // method name
       "0\uffff"; // param count
+  
+  /**
+   * Tests that out-of-range or other illegal integer values generated
+   * by client-side serialization get a nested exception with a reasonable
+   * error message.
+   */
+  public void testDecodeBadIntegerValue() {
+    String requestBase = "" +
+        AbstractSerializationStream.SERIALIZATION_STREAM_VERSION +
+        RPC_SEPARATOR_CHAR + // version
+        "0" + RPC_SEPARATOR_CHAR + // flags
+        "6" + RPC_SEPARATOR_CHAR + // string table entry count
+        WrapperIF.class.getName() + RPC_SEPARATOR_CHAR + // string table entry #1
+        "method1" + RPC_SEPARATOR_CHAR + // string table entry #2
+        "moduleBaseURL" + RPC_SEPARATOR_CHAR + // string table entry #3
+        "whitelistHashcode" + RPC_SEPARATOR_CHAR + // string table entry #4
+        Wrapper.class.getName() + RPC_SEPARATOR_CHAR + // string table entry #5
+        Wrapper.class.getName() +
+        "/316143997" + RPC_SEPARATOR_CHAR + // string table entry #6
+        "3" + RPC_SEPARATOR_CHAR + // module base URL
+        "4" + RPC_SEPARATOR_CHAR + // whitelist hashcode
+        "1" + RPC_SEPARATOR_CHAR + // interface name
+        "2" + RPC_SEPARATOR_CHAR + // method name
+        "1" + RPC_SEPARATOR_CHAR + // param count
+        "5" + RPC_SEPARATOR_CHAR + // IntWrapper class name
+        "6" + RPC_SEPARATOR_CHAR; // IntWrapper signature
+    
+    // Valid values
+    String goodRequest = requestBase + "12" + RPC_SEPARATOR_CHAR + // byte
+    "345" + RPC_SEPARATOR_CHAR + // char
+    "678" + RPC_SEPARATOR_CHAR + // short
+    "9101112" + RPC_SEPARATOR_CHAR; // int
+    
+    RPC.decodeRequest(goodRequest); // should succeed
+    
+    // Create bad RPC messages with out of range, fractional, and non-numerical
+    // values for byte, char, short, and int fields.
+    for (int idx = 0; idx < 12; idx++) {
+      String b = "12";
+      String c = "345";
+      String s = "678";
+      String i = "9101112";
+      String message = null;
+      String badValue = null;
+      
+      // Choose type of bad value and expected error message string
+      switch (idx / 4) {
+        case 0:
+          badValue = "123456789123456789";
+          message = "out-of-range";
+          break;
+        case 1:
+          badValue = "1.25";
+          message = "fractional";
+          break;
+        case 2:
+          badValue = "123ABC";
+          message = "non-numerical";
+          break;
+      }
+      
+      // Choose field to hold bad value
+      switch (idx % 4) {
+        case 0: b = badValue; break;
+        case 1: c = badValue; break;
+        case 2: s = badValue; break;
+        case 3: i = badValue; break;
+      }
+      
+      // Form the request
+      String request = requestBase + b + RPC_SEPARATOR_CHAR + // byte
+          c + RPC_SEPARATOR_CHAR + // char
+          s + RPC_SEPARATOR_CHAR + // short
+          i + RPC_SEPARATOR_CHAR; // int
+      
+      // Check that request fails with the expected message
+      try {
+        RPC.decodeRequest(request);
+        fail();
+      } catch (IncompatibleRemoteServiceException e) {
+        assertTrue(e.getMessage().contains(message));
+      }
+    }
+  }
 
   /**
    * Tests that seeing obsolete RPC formats throws an