Add opcode-emission backward compatibility.

This makes it so that when you pass dx --target-api=N, where N is an
API level representing Honeycomb or earlier, dx will not emit any of
the new extended opcodes.

N is currently baked into the code as 12 or larger being
post-Honeycomb, but it is subject to change if there are more revs of
the API under the Honeycomb umbrella (which wouldn't be surprising).

Bug: 4094709
Change-Id: Iaf5177f179b22586bcf806ecb53de20b6e989777
diff --git a/dx/src/com/android/dx/dex/code/Dops.java b/dx/src/com/android/dx/dex/code/Dops.java
index df88186..d460efe 100644
--- a/dx/src/com/android/dx/dex/code/Dops.java
+++ b/dx/src/com/android/dx/dex/code/Dops.java
@@ -1404,13 +1404,27 @@
      * the last in its chain
      */
     public static Dop getNextOrNull(Dop opcode, DexOptions options) {
-        int nextOpcode = opcode.getNextOpcode();
+        for (;;) {
+            int nextOpcode = opcode.getNextOpcode();
 
-        if (nextOpcode == Opcodes.NO_NEXT) {
-            return null;
+            if (nextOpcode == Opcodes.NO_NEXT) {
+                return null;
+            }
+
+            opcode = get(nextOpcode);
+
+            if (!options.enableExtendedOpcodes && Opcodes.isExtended(nextOpcode)) {
+                /*
+                 * Continuing rather than just returning null here
+                 * protects against the possibility that an
+                 * instruction fitting chain might list non-extended
+                 * opcodes after extended ones.
+                 */
+                continue;
+            }
+
+            return opcode;
         }
-
-        return get(nextOpcode);
     }
 
     /**
diff --git a/dx/src/com/android/dx/io/Opcodes.java b/dx/src/com/android/dx/io/Opcodes.java
index bf0aa66..f79394c 100644
--- a/dx/src/com/android/dx/io/Opcodes.java
+++ b/dx/src/com/android/dx/io/Opcodes.java
@@ -362,8 +362,23 @@
     }
 
     /**
+     * Gets whether ({@code true}) or not ({@code false}) the given opcode value
+     * is an "extended" opcode. Extended opcodes require a full 16-bit code unit
+     * to represent (without leaving space for an argument byte).
+     * 
+     * @param opcode the opcode value
+     * @return {@code true} iff the opcode is an "extended" opcode
+     */
+    public static boolean isExtended(int opcode) {
+        return (opcode >= 0x00ff);
+    }
+
+    /**
      * Gets the opcode out of an opcode unit, the latter of which may also
      * include one or more argument values.
+     *
+     * @param opcodeUnit the opcode-containing code unit
+     * @return the extracted opcode
      */
     public static int extractOpcodeFromUnit(int opcodeUnit) {
         /*
diff --git a/dx/tests/120-disable-extended-ops/Blort.java b/dx/tests/120-disable-extended-ops/Blort.java
new file mode 100644
index 0000000..a1cfa36
--- /dev/null
+++ b/dx/tests/120-disable-extended-ops/Blort.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2011 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 Blort
+{
+    private int field0 = 0;
+    private int field1 = 1;
+    private int field2 = 2;
+    private int field3 = 3;
+    private int field4 = 4;
+    private int field5 = 5;
+    private int field6 = 6;
+    private int field7 = 7;
+    private int field8 = 8;
+    private int field9 = 9;
+
+    public void test() {
+        int v0 = field0;
+        int v1 = field1;
+        int v2 = field2;
+        int v3 = field3;
+        int v4 = field4;
+        int v5 = field5;
+        int v6 = field6;
+        int v7 = field7;
+        int v8 = field8;
+        int v9 = field9;
+        int v10 = field0;
+        int v11 = field1;
+        int v12 = field2;
+        int v13 = field3;
+        int v14 = field4;
+        int v15 = field5;
+        int v16 = field6;
+        int v17 = field7;
+        int v18 = field8;
+        int v19 = field9;
+
+        sink(v0);
+        sink(v1);
+        sink(v2);
+        sink(v3);
+        sink(v4);
+        sink(v5);
+        sink(v6);
+        sink(v7);
+        sink(v8);
+        sink(v9);
+        sink(v10);
+        sink(v11);
+        sink(v12);
+        sink(v13);
+        sink(v14);
+        sink(v15);
+        sink(v16);
+        sink(v17);
+        sink(v18);
+        sink(v19);
+    }
+
+    private static void sink(int arg) {
+        // This space intentionally left blank.
+    }
+}
diff --git a/dx/tests/120-disable-extended-ops/expected.txt b/dx/tests/120-disable-extended-ops/expected.txt
new file mode 100644
index 0000000..a965a70
--- /dev/null
+++ b/dx/tests/120-disable-extended-ops/expected.txt
@@ -0,0 +1 @@
+Done
diff --git a/dx/tests/120-disable-extended-ops/info.txt b/dx/tests/120-disable-extended-ops/info.txt
new file mode 100644
index 0000000..213c29c
--- /dev/null
+++ b/dx/tests/120-disable-extended-ops/info.txt
@@ -0,0 +1,3 @@
+This is a test of the dx option --target-api, which is supposed to
+disable the emission of extended-opcode instructions when the target
+API is for Honeycomb or earlier.
diff --git a/dx/tests/120-disable-extended-ops/run b/dx/tests/120-disable-extended-ops/run
new file mode 100644
index 0000000..c0116c8
--- /dev/null
+++ b/dx/tests/120-disable-extended-ops/run
@@ -0,0 +1,37 @@
+#!/bin/bash
+#
+# Copyright (C) 2011 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.
+
+# This test checks to see that without --target-api=11, the result of
+# dx contains at least one "iget/jumbo" opcode (one example of an
+# extended opcode); and that with that option no such opcode is
+# produced.
+
+$JAVAC -d . *.java
+
+count=`dx --debug --dex --no-optimize --positions=none --no-locals \
+    --dump-method=Blort.test Blort.class | grep "iget/jumbo" | wc -l`
+if [ "$count" = "0" ]; then
+    echo "No iget/jumbo emitted without --target-api"
+fi
+
+count=`dx --debug --dex --no-optimize --positions=none --no-locals \
+    --target-api=11 \
+    --dump-method=Blort.test Blort.class | grep "iget/jumbo" | wc -l`
+if [ "$count" != "0" ]; then
+    echo "Found $count iget/jumbo emitted with --target-api=11"
+fi
+
+echo "Done"