More tests for detail messages, plus a new one.

The array-length instruction is likely to encounter nulls.

Change-Id: I628f5f00dfaff9414740e2f7015b9fb3d34a1bc9
diff --git a/src/compiler.cc b/src/compiler.cc
index 4d98d61..aa465c3 100644
--- a/src/compiler.cc
+++ b/src/compiler.cc
@@ -245,9 +245,7 @@
   }
 
   // Capitalize the instruction set, because that's what we do in the build system.
-  std::ostringstream instruction_set_name_os;
-  instruction_set_name_os << instruction_set;
-  std::string instruction_set_name(instruction_set_name_os.str());
+  std::string instruction_set_name(ToStr<InstructionSet>(instruction_set).str());
   for (size_t i = 0; i < instruction_set_name.size(); ++i) {
     instruction_set_name[i] = toupper(instruction_set_name[i]);
   }
diff --git a/src/runtime_support.cc b/src/runtime_support.cc
index 903bbf9..06908c9 100644
--- a/src/runtime_support.cc
+++ b/src/runtime_support.cc
@@ -104,14 +104,12 @@
                                                       const Method* caller,
                                                       const Method* called,
                                                       InvokeType type) {
-  std::ostringstream type_stream;
-  type_stream << type;
   self->ThrowNewExceptionF("Ljava/lang/IllegalAccessError;",
                            "illegal class access ('%s' -> '%s')"
                            "in attempt to invoke %s method '%s' from '%s'",
                            PrettyDescriptor(referrer).c_str(),
                            PrettyDescriptor(accessed).c_str(),
-                           type_stream.str().c_str(),
+                           ToStr<InvokeType>(type).c_str(),
                            PrettyMethod(called).c_str(),
                            PrettyMethod(caller).c_str());
 }
@@ -169,11 +167,9 @@
                                               InvokeType type) {
   const DexFile& dex_file =
       Runtime::Current()->GetClassLinker()->FindDexFile(caller->GetDeclaringClass()->GetDexCache());
-  std::ostringstream type_stream;
-  type_stream << type;
   self->ThrowNewExceptionF("Ljava/lang/NullPointerException;",
                            "Attempt to invoke %s method '%s' on a null object reference",
-                           type_stream.str().c_str(),
+                           ToStr<InvokeType>(type).c_str(),
                            PrettyMethod(method_idx, dex_file, true).c_str());
 }
 
@@ -235,6 +231,10 @@
       self->ThrowNewException("Ljava/lang/NullPointerException;",
                               "Attempt to write to null array");
       break;
+    case Instruction::ARRAY_LENGTH:
+      self->ThrowNewException("Ljava/lang/NullPointerException;",
+                              "Attempt to get length of null array");
+      break;
     default: {
       const DexFile& dex_file = Runtime::Current()->GetClassLinker()
           ->FindDexFile(throw_method->GetDeclaringClass()->GetDexCache());
diff --git a/test/201-built-in-exception-detail-messages/src/Main.java b/test/201-built-in-exception-detail-messages/src/Main.java
index 638fb47..9b67db6 100644
--- a/test/201-built-in-exception-detail-messages/src/Main.java
+++ b/test/201-built-in-exception-detail-messages/src/Main.java
@@ -24,6 +24,8 @@
     arrayStore();
     classCast();
     classNotFound();
+    negativeArraySize();
+    nullPointers();
     reflection();
     stringIndex();
   }
@@ -275,6 +277,71 @@
     }
   }
 
+  private static void negativeArraySize() throws Exception {
+    try {
+      int[] is = new int[-123];
+      fail();
+    } catch (NegativeArraySizeException ex) {
+      assertEquals("-123", ex.getMessage());
+    }
+  }
+
+  private static void nullPointers() throws Exception {
+    // Invoke method.
+    try {
+      Object o = null;
+      o.hashCode();
+      fail();
+    } catch (NullPointerException ex) {
+      assertEquals("Attempt to invoke virtual method 'int java.lang.Object.hashCode()' on a null object reference", ex.getMessage());
+    }
+
+    // Read field.
+    try {
+      A a = null;
+      int i = a.i;
+      fail();
+    } catch (NullPointerException ex) {
+      assertEquals("Attempt to read from field 'int A.i' on a null object reference", ex.getMessage());
+    }
+
+    // Write field.
+    try {
+      A a = null;
+      a.i = 1;
+      fail();
+    } catch (NullPointerException ex) {
+      assertEquals("Attempt to write to field 'int A.i' on a null object reference", ex.getMessage());
+    }
+
+    // Read array.
+    try {
+      int[] is = null;
+      int i = is[0];
+      fail();
+    } catch (NullPointerException ex) {
+      assertEquals("Attempt to read from null array", ex.getMessage());
+    }
+
+    // Write array.
+    try {
+      int[] is = null;
+      is[0] = 1;
+      fail();
+    } catch (NullPointerException ex) {
+      assertEquals("Attempt to write to null array", ex.getMessage());
+    }
+
+    // Invoke method.
+    try {
+      int[] is = null;
+      int i = is.length;
+      fail();
+    } catch (NullPointerException ex) {
+      assertEquals("Attempt to get length of null array", ex.getMessage());
+    }
+  }
+
   private static void reflection() throws Exception {
     // Can't assign Integer to a String field.
     try {
@@ -320,6 +387,42 @@
     } catch (IllegalArgumentException expected) {
       assertEquals("method A.m argument 1 has type int, got null", expected.getMessage());
     }
+
+    try {
+      Method m = String.class.getMethod("charAt", int.class);
+      m.invoke("hello"); // Wrong number of arguments.
+      fail();
+    } catch (IllegalArgumentException iae) {
+      assertEquals("wrong number of arguments; expected 1, got 0", iae.getMessage());
+    }
+    try {
+      Method m = String.class.getMethod("charAt", int.class);
+      m.invoke("hello", "world"); // Wrong type.
+      fail();
+    } catch (IllegalArgumentException iae) {
+      assertEquals("method java.lang.String.charAt argument 1 has type int, got java.lang.String", iae.getMessage());
+    }
+    try {
+      Method m = String.class.getMethod("charAt", int.class);
+      m.invoke("hello", (Object) null); // Null for a primitive argument.
+      fail();
+    } catch (IllegalArgumentException iae) {
+      assertEquals("method java.lang.String.charAt argument 1 has type int, got null", iae.getMessage());
+    }
+    try {
+      Method m = String.class.getMethod("charAt", int.class);
+      m.invoke(new Integer(5)); // Wrong type for 'this'.
+      fail();
+    } catch (IllegalArgumentException iae) {
+      assertEquals("expected receiver of type java.lang.String, but got java.lang.Integer", iae.getMessage());
+    }
+    try {
+      Method m = String.class.getMethod("charAt", int.class);
+      m.invoke(null); // Null for 'this'.
+      fail();
+    } catch (NullPointerException npe) {
+      assertEquals("expected receiver of type java.lang.String, but got null", npe.getMessage());
+    }
   }
 
   private static void stringIndex() throws Exception {