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 {