Fix MULTIANEWARRAY instruction w.r.t. dx.

Summary:
DX converts the MULTIANEWARRAY instruction into a call to the
Array.newInstance(Class,int...) method.  Since DalvikStatsTool is
tracking the method counts, it needs to add this method to the mix
when it notices a MULTIANEWARRAY instruction to keep the method counts
correct.

Test Plan: added unit test
diff --git a/src/com/facebook/buck/dalvik/DalvikAwareOutputStreamHelper.java b/src/com/facebook/buck/dalvik/DalvikAwareOutputStreamHelper.java
index f560667..b732898 100644
--- a/src/com/facebook/buck/dalvik/DalvikAwareOutputStreamHelper.java
+++ b/src/com/facebook/buck/dalvik/DalvikAwareOutputStreamHelper.java
@@ -38,8 +38,7 @@
  */
 public class DalvikAwareOutputStreamHelper implements ZipOutputStreamHelper {
 
-  // TODO-mmarucheck (#3178515): remove the '-2' when we understand the missing references.
-  private static final int MAX_METHOD_REFERENCES = 64 * 1024 - 2;
+  private static final int MAX_METHOD_REFERENCES = 64 * 1024;
 
   private final ZipOutputStream outStream;
   private final Set<String> entryNames = Sets.newHashSet();
diff --git a/src/com/facebook/buck/dalvik/DalvikStatsTool.java b/src/com/facebook/buck/dalvik/DalvikStatsTool.java
index 9bafc53..2fd93df 100644
--- a/src/com/facebook/buck/dalvik/DalvikStatsTool.java
+++ b/src/com/facebook/buck/dalvik/DalvikStatsTool.java
@@ -16,6 +16,7 @@
 
 package com.facebook.buck.dalvik;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 
@@ -24,9 +25,11 @@
 import org.objectweb.asm.FieldVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.reflect.Array;
 import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
@@ -49,6 +52,14 @@
       Pattern.compile("Activity$"), 1100
   );
 
+  // DX translates MULTIANEWARRAY into a method call that matches this (owner,name,desc)
+  private static final String MULTIARRAY_OWNER = Type.getType(Array.class).getInternalName();
+  private static final String MULTIARRAY_NAME = "newInstance";
+  private static final String MULTIARRAY_DESC = Type.getMethodType(
+      Type.getType(Object.class),
+      Type.getType(Class.class),
+      Type.getType("[" + Type.INT_TYPE.getDescriptor())).getDescriptor();
+
   public static class MethodReference {
 
     public final String className;
@@ -123,7 +134,7 @@
             continue;
           }
           InputStream rawClass = inJar.getInputStream(entry);
-          int footprint = getEstimate(rawClass, PENALTIES).estimatedLinearAllocSize;
+          int footprint = getEstimate(rawClass).estimatedLinearAllocSize;
           System.out.println(footprint + "\t" + entry.getName().replace(".class", ""));
         }
       }
@@ -138,26 +149,23 @@
    * @return the estimate
    */
   public static Stats getEstimate(InputStream rawClass) throws IOException {
-    return getEstimate(rawClass, PENALTIES);
+    ClassReader classReader = new ClassReader(rawClass);
+    return getEstimateInternal(classReader);
   }
 
   /**
    * Estimates the footprint that a given class will have in the LinearAlloc buffer
    * of Android's Dalvik VM.
    *
-   * @param rawClass Raw bytes of the Java class to analyze.
-   * @param penalties Map from regex patterns to run against the internal name of the class and
-   *                  its parent to a "penalty" to apply to the footprint, representing the size
-   *                  of the vtable of the parent class.
+   * @param classReader reader containing the Java class to analyze.
    * @return the estimate
    */
-  private static Stats getEstimate(
-      InputStream rawClass,
-      ImmutableMap<Pattern, Integer> penalties) throws IOException {
+  @VisibleForTesting
+  static Stats getEstimateInternal(ClassReader classReader) throws IOException {
     // SKIP_FRAMES was required to avoid an exception in ClassReader when running on proguard
     // output. We don't need to visit frames so this isn't an issue.
-    StatsClassVisitor statsVisitor = new StatsClassVisitor(penalties);
-    new ClassReader(rawClass).accept(statsVisitor, ClassReader.SKIP_FRAMES);
+    StatsClassVisitor statsVisitor = new StatsClassVisitor(PENALTIES);
+    classReader.accept(statsVisitor, ClassReader.SKIP_FRAMES);
     return new Stats(
         statsVisitor.footprint,
         statsVisitor.methodReferenceBuilder.build());
@@ -264,6 +272,14 @@
         super.visitMethodInsn(opcode, owner, name, desc);
         methodReferenceBuilder.add(new MethodReference(owner, name, desc));
       }
+
+      @Override
+      public void visitMultiANewArrayInsn(String desc, int dims) {
+        // dx translates this instruction into a method invocation on
+        // Array.newInstance(Class clazz, int...dims);
+        methodReferenceBuilder.add(
+            new MethodReference(MULTIARRAY_OWNER, MULTIARRAY_NAME, MULTIARRAY_DESC));
+      }
     }
   }
 }
diff --git a/test/com/facebook/buck/dalvik/BUCK b/test/com/facebook/buck/dalvik/BUCK
index 7461310..98f76af 100644
--- a/test/com/facebook/buck/dalvik/BUCK
+++ b/test/com/facebook/buck/dalvik/BUCK
@@ -10,5 +10,6 @@
     '//lib:junit',
     '//src/com/facebook/buck/dalvik:dalvik',
     '//src/com/facebook/buck/dalvik:dalvik_stats_tool',
+    '//third-party/java/asm:asm',
   ],
 )
diff --git a/test/com/facebook/buck/dalvik/DalvikStatsToolTest.java b/test/com/facebook/buck/dalvik/DalvikStatsToolTest.java
index 872fb65..aa34903 100644
--- a/test/com/facebook/buck/dalvik/DalvikStatsToolTest.java
+++ b/test/com/facebook/buck/dalvik/DalvikStatsToolTest.java
@@ -28,10 +28,13 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
+import org.objectweb.asm.ClassReader;
 
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.IOException;
 import java.io.InputStream;
+import java.lang.reflect.Array;
 import java.net.URI;
 import java.util.Set;
 
@@ -148,6 +151,47 @@
   }
 
   /**
+   * Verifies that we count the MULTIANEWARRAY instruction (used in UsesMultiANewArray) as
+   * a call to Array.newInstance(Class, int...dims).  We do this by also measuring a class
+   * that uses MULTIANEWARRAY and calls Array.newInstance explicitly, and verify that the
+   * two classes have the same number of methodReferences.
+   */
+  @Test
+  public void testMultiANewArray() throws IOException {
+    // Avoid unused method warnings for types we statically analyze.
+    UsesMultiANewArray.createMultiArray();
+    UsesMultiANewArrayAndExplicitArrayCall.createMultiArray();
+
+    ClassReader usesImplicitOnly = new ClassReader(UsesMultiANewArray.class.getName());
+    DalvikStatsTool.Stats implicitStats = DalvikStatsTool.getEstimateInternal(usesImplicitOnly);
+
+    ClassReader usesBoth = new ClassReader(UsesMultiANewArrayAndExplicitArrayCall.class.getName());
+    DalvikStatsTool.Stats bothStats = DalvikStatsTool.getEstimateInternal(usesBoth);
+
+    assertEquals(implicitStats.methodReferences.size(), bothStats.methodReferences.size());
+  }
+
+  /**
+   * A test class that uses the MULTIANEWARRAY instruction but calls no methods explicitly.
+   */
+  private static class UsesMultiANewArray {
+    static Object createMultiArray() {
+      return new Object[1][1];
+    }
+  }
+
+  /**
+   * A test class that uses MULTINEWARRAY and also calls Array.newInstance(Class, int...)
+   * explicitly.
+   */
+  private static class UsesMultiANewArrayAndExplicitArrayCall {
+    static Object createMultiArray() {
+      Array.newInstance(Object.class, 1, 1);
+      return new Object[1][1];
+    }
+  }
+
+  /**
    * A file object used to represent source coming from a string.
    */
   public class JavaSourceFromString extends SimpleJavaFileObject {