Transform incorrect invokevirtual ops to invokedirect.
bug: 12370565
Change-Id: I75de311912d3e620f95fbac712e799e64622e4ab
diff --git a/dx/src/com/android/dx/cf/code/Ropper.java b/dx/src/com/android/dx/cf/code/Ropper.java
index c318b4c..6a20f27 100644
--- a/dx/src/com/android/dx/cf/code/Ropper.java
+++ b/dx/src/com/android/dx/cf/code/Ropper.java
@@ -16,6 +16,7 @@
package com.android.dx.cf.code;
+import com.android.dx.cf.iface.MethodList;
import com.android.dx.rop.code.AccessFlags;
import com.android.dx.rop.code.BasicBlock;
import com.android.dx.rop.code.BasicBlockList;
@@ -257,12 +258,14 @@
*
* @param method {@code non-null;} method to convert
* @param advice {@code non-null;} translation advice to use
+ * @param methods {@code non-null;} list of methods defined by the class
+ * that defines {@code method}.
* @return {@code non-null;} the converted instance
*/
public static RopMethod convert(ConcreteMethod method,
- TranslationAdvice advice) {
+ TranslationAdvice advice, MethodList methods) {
try {
- Ropper r = new Ropper(method, advice);
+ Ropper r = new Ropper(method, advice, methods);
r.doit();
return r.getRopMethod();
} catch (SimException ex) {
@@ -278,8 +281,10 @@
*
* @param method {@code non-null;} method to convert
* @param advice {@code non-null;} translation advice to use
+ * @param methods {@code non-null;} list of methods defined by the class
+ * that defines {@code method}.
*/
- private Ropper(ConcreteMethod method, TranslationAdvice advice) {
+ private Ropper(ConcreteMethod method, TranslationAdvice advice, MethodList methods) {
if (method == null) {
throw new NullPointerException("method == null");
}
@@ -292,7 +297,7 @@
this.blocks = BasicBlocker.identifyBlocks(method);
this.maxLabel = blocks.getMaxLabel();
this.maxLocals = method.getMaxLocals();
- this.machine = new RopperMachine(this, method, advice);
+ this.machine = new RopperMachine(this, method, advice, methods);
this.sim = new Simulator(machine, method);
this.startFrames = new Frame[maxLabel];
this.subroutines = new Subroutine[maxLabel];
diff --git a/dx/src/com/android/dx/cf/code/RopperMachine.java b/dx/src/com/android/dx/cf/code/RopperMachine.java
index f1b8db4..ea7d6ff 100644
--- a/dx/src/com/android/dx/cf/code/RopperMachine.java
+++ b/dx/src/com/android/dx/cf/code/RopperMachine.java
@@ -16,6 +16,9 @@
package com.android.dx.cf.code;
+import com.android.dx.cf.iface.Method;
+import com.android.dx.cf.iface.MethodList;
+import com.android.dx.rop.code.AccessFlags;
import com.android.dx.rop.code.FillArrayDataInsn;
import com.android.dx.rop.code.Insn;
import com.android.dx.rop.code.PlainCstInsn;
@@ -67,6 +70,9 @@
/** {@code non-null;} method being converted */
private final ConcreteMethod method;
+ /** {@code non-null:} list of methods from the class whose method is being converted */
+ private final MethodList methods;
+
/** {@code non-null;} translation advice */
private final TranslationAdvice advice;
@@ -122,11 +128,17 @@
* @param ropper {@code non-null;} ropper controlling this instance
* @param method {@code non-null;} method being converted
* @param advice {@code non-null;} translation advice to use
+ * @param methods {@code non-null;} list of methods defined by the class
+ * that defines {@code method}.
*/
public RopperMachine(Ropper ropper, ConcreteMethod method,
- TranslationAdvice advice) {
+ TranslationAdvice advice, MethodList methods) {
super(method.getEffectiveDescriptor());
+ if (methods == null) {
+ throw new NullPointerException("methods == null");
+ }
+
if (ropper == null) {
throw new NullPointerException("ropper == null");
}
@@ -137,6 +149,7 @@
this.ropper = ropper;
this.method = method;
+ this.methods = methods;
this.advice = advice;
this.maxLocals = method.getMaxLocals();
this.insns = new ArrayList<Insn>(25);
@@ -907,6 +920,35 @@
return RegOps.PUT_FIELD;
}
case ByteOps.INVOKEVIRTUAL: {
+ CstMethodRef ref = (CstMethodRef) cst;
+ // The java bytecode specification does not explicitly disallow
+ // invokevirtual calls to any instance method, though it
+ // specifies that instance methods and private methods "should" be
+ // called using "invokespecial" instead of "invokevirtual".
+ // Several bytecode tools generate "invokevirtual" instructions for
+ // invocation of private methods.
+ //
+ // The dalvik opcode specification on the other hand allows
+ // invoke-virtual to be used only with "normal" virtual methods,
+ // i.e, ones that are not private, static, final or constructors.
+ // We therefore need to transform invoke-virtual calls to private
+ // instance methods to invoke-direct opcodes.
+ //
+ // Note that it assumes that all methods for a given class are
+ // defined in the same dex file.
+ //
+ // NOTE: This is a slow O(n) loop, and can be replaced with a
+ // faster implementation (at the cost of higher memory usage)
+ // if it proves to be a hot area of code.
+ if (ref.getDefiningClass() == method.getDefiningClass()) {
+ for (int i = 0; i < methods.size(); ++i) {
+ final Method m = methods.get(i);
+ if (AccessFlags.isPrivate(m.getAccessFlags()) &&
+ ref.getNat().equals(m.getNat())) {
+ return RegOps.INVOKE_DIRECT;
+ }
+ }
+ }
return RegOps.INVOKE_VIRTUAL;
}
case ByteOps.INVOKESPECIAL: {
diff --git a/dx/src/com/android/dx/command/dump/BlockDumper.java b/dx/src/com/android/dx/command/dump/BlockDumper.java
index b670cd7..8d19c45 100644
--- a/dx/src/com/android/dx/command/dump/BlockDumper.java
+++ b/dx/src/com/android/dx/command/dump/BlockDumper.java
@@ -285,7 +285,7 @@
TranslationAdvice advice = DexTranslationAdvice.THE_ONE;
BytecodeArray code = meth.getCode();
ByteArray bytes = code.getBytes();
- RopMethod rmeth = Ropper.convert(meth, advice);
+ RopMethod rmeth = Ropper.convert(meth, advice, classFile.getMethods());
StringBuffer sb = new StringBuffer(2000);
if (optimize) {
diff --git a/dx/src/com/android/dx/command/dump/DotDumper.java b/dx/src/com/android/dx/command/dump/DotDumper.java
index 1c4c74b..eb9f00d 100644
--- a/dx/src/com/android/dx/command/dump/DotDumper.java
+++ b/dx/src/com/android/dx/command/dump/DotDumper.java
@@ -115,7 +115,7 @@
TranslationAdvice advice = DexTranslationAdvice.THE_ONE;
RopMethod rmeth =
- Ropper.convert(meth, advice);
+ Ropper.convert(meth, advice, classFile.getMethods());
if (optimize) {
boolean isStatic = AccessFlags.isStatic(meth.getAccessFlags());
diff --git a/dx/src/com/android/dx/command/dump/SsaDumper.java b/dx/src/com/android/dx/command/dump/SsaDumper.java
index 80953c1..76f919c 100644
--- a/dx/src/com/android/dx/command/dump/SsaDumper.java
+++ b/dx/src/com/android/dx/command/dump/SsaDumper.java
@@ -90,7 +90,7 @@
ConcreteMethod meth =
new ConcreteMethod((Method) member, classFile, true, true);
TranslationAdvice advice = DexTranslationAdvice.THE_ONE;
- RopMethod rmeth = Ropper.convert(meth, advice);
+ RopMethod rmeth = Ropper.convert(meth, advice, classFile.getMethods());
SsaMethod ssaMeth = null;
boolean isStatic = AccessFlags.isStatic(meth.getAccessFlags());
int paramWidth = computeParamWidth(meth, isStatic);
diff --git a/dx/src/com/android/dx/dex/cf/CfTranslator.java b/dx/src/com/android/dx/dex/cf/CfTranslator.java
index 92ea0f7..1d2542d 100644
--- a/dx/src/com/android/dx/dex/cf/CfTranslator.java
+++ b/dx/src/com/android/dx/dex/cf/CfTranslator.java
@@ -279,7 +279,7 @@
advice = DexTranslationAdvice.THE_ONE;
- RopMethod rmeth = Ropper.convert(concrete, advice);
+ RopMethod rmeth = Ropper.convert(concrete, advice, methods);
RopMethod nonOptRmeth = null;
int paramSize;
diff --git a/dx/tests/123-dex-transform-invalid-virtual-to-direct/Zorch.class b/dx/tests/123-dex-transform-invalid-virtual-to-direct/Zorch.class
new file mode 100644
index 0000000..cd60b9a
--- /dev/null
+++ b/dx/tests/123-dex-transform-invalid-virtual-to-direct/Zorch.class
Binary files differ
diff --git a/dx/tests/123-dex-transform-invalid-virtual-to-direct/Zorch.java b/dx/tests/123-dex-transform-invalid-virtual-to-direct/Zorch.java
new file mode 100644
index 0000000..a163cb0
--- /dev/null
+++ b/dx/tests/123-dex-transform-invalid-virtual-to-direct/Zorch.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed 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.
+ */
+
+public class Zorch
+{
+ public void zorch1() {
+ zorch2(0);
+ }
+
+ private int zorch2(int x) {
+ return 0;
+ }
+}
diff --git a/dx/tests/123-dex-transform-invalid-virtual-to-direct/expected.txt b/dx/tests/123-dex-transform-invalid-virtual-to-direct/expected.txt
new file mode 100644
index 0000000..8c39333
--- /dev/null
+++ b/dx/tests/123-dex-transform-invalid-virtual-to-direct/expected.txt
@@ -0,0 +1,8 @@
+Zorch.zorch1:()V:
+regs: 0004; ins: 0001; outs: 0002
+ 0000: move-object v0, v3
+ 0001: move-object v1, v0
+ 0002: const/4 v2, #int 0 // #0
+ 0003: invoke-direct {v1, v2}, Zorch.zorch2:(I)I
+ 0006: move-result v1
+ 0007: return-void
diff --git a/dx/tests/123-dex-transform-invalid-virtual-to-direct/info.txt b/dx/tests/123-dex-transform-invalid-virtual-to-direct/info.txt
new file mode 100644
index 0000000..4b882cf
--- /dev/null
+++ b/dx/tests/123-dex-transform-invalid-virtual-to-direct/info.txt
@@ -0,0 +1,10 @@
+This test checks that we convert invoke-virtual calls to private
+methods with invoke-direct calls.
+
+The Zorch.class file is manually created by compiling Zorch.java
+with java6 and manually editing the file to replace the opcode at
+position 0xE9 with 0xB6 (invokevirtual) instead of 0xB7 (invokespecial).
+
+This test compares emitted code against a known-good (via eyeballing)
+version, so it is possible for this test to spuriously fail if other
+aspects of conversion end up altering the output in innocuous ways.
diff --git a/dx/tests/123-dex-transform-invalid-virtual-to-direct/run b/dx/tests/123-dex-transform-invalid-virtual-to-direct/run
new file mode 100644
index 0000000..71ff62f
--- /dev/null
+++ b/dx/tests/123-dex-transform-invalid-virtual-to-direct/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed 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.
+
+dx --debug --dex --no-optimize --positions=none --no-locals \
+ --dump-method=Zorch.zorch1 Zorch.class