Allow invokevirtual, invokespecial, invokestatic on interface methods

Allow default and static methods on interfaces if
--min-sdk-version >= 24.

Test: make checkbuild
Change-Id: I6b617a37256bdb95f4c11e58fe2ebf08cf7aa324
diff --git a/dx/src/com/android/dx/Version.java b/dx/src/com/android/dx/Version.java
index 0e77941..88225e6 100644
--- a/dx/src/com/android/dx/Version.java
+++ b/dx/src/com/android/dx/Version.java
@@ -21,5 +21,5 @@
  */
 public class Version {
     /** {@code non-null;} version string */
-    public static final String VERSION = "1.12";
+    public static final String VERSION = "1.13";
 }
diff --git a/dx/src/com/android/dx/cf/code/RopperMachine.java b/dx/src/com/android/dx/cf/code/RopperMachine.java
index d554d91..7ed6328 100644
--- a/dx/src/com/android/dx/cf/code/RopperMachine.java
+++ b/dx/src/com/android/dx/cf/code/RopperMachine.java
@@ -958,6 +958,10 @@
                  * on "invokespecial" as well as section 4.8.2 (7th
                  * bullet point) for the gory details.
                  */
+                /* TODO: Consider checking that invoke-special target
+                 * method is private, or constructor since otherwise ART
+                 * verifier will reject it.
+                 */
                 CstMethodRef ref = (CstMethodRef) cst;
                 if (ref.isInstanceInit() ||
                     (ref.getDefiningClass().equals(method.getDefiningClass()))) {
diff --git a/dx/src/com/android/dx/cf/code/Simulator.java b/dx/src/com/android/dx/cf/code/Simulator.java
index 46ab4df..b858bb0 100644
--- a/dx/src/com/android/dx/cf/code/Simulator.java
+++ b/dx/src/com/android/dx/cf/code/Simulator.java
@@ -654,32 +654,30 @@
                     machine.popArgs(frame, Type.OBJECT, fieldType);
                     break;
                 }
-                case ByteOps.INVOKEINTERFACE: {
-                    /*
-                     * Convert the interface method ref into a normal
-                     * method ref.
-                     */
-                    cst = ((CstInterfaceMethodRef) cst).toMethodRef();
-                    // and fall through...
-                }
+                case ByteOps.INVOKEINTERFACE:
                 case ByteOps.INVOKEVIRTUAL:
-                case ByteOps.INVOKESPECIAL: {
-                    /*
-                     * Get the instance prototype, and use it to direct
-                     * the machine.
-                     */
-                    Prototype prototype =
-                        ((CstMethodRef) cst).getPrototype(false);
-                    machine.popArgs(frame, prototype);
-                    break;
-                }
+                case ByteOps.INVOKESPECIAL:
                 case ByteOps.INVOKESTATIC: {
                     /*
-                     * Get the static prototype, and use it to direct
-                     * the machine.
+                     * Convert the interface method ref into a normal
+                     * method ref if necessary.
                      */
+                    if (cst instanceof CstInterfaceMethodRef) {
+                        if (opcode != ByteOps.INVOKEINTERFACE) {
+                            if (!dexOptions.canUseDefaultInterfaceMethods()) {
+                                throw new SimException(
+                                    "default or static interface method used without --min-sdk-version >= 24");
+                            }
+                        }
+                        cst = ((CstInterfaceMethodRef) cst).toMethodRef();
+                    }
+                    /*
+                     * Get the instance or static prototype, and use it to
+                     * direct the machine.
+                     */
+                    boolean staticMethod = (opcode == ByteOps.INVOKESTATIC);
                     Prototype prototype =
-                        ((CstMethodRef) cst).getPrototype(true);
+                        ((CstMethodRef) cst).getPrototype(staticMethod);
                     machine.popArgs(frame, prototype);
                     break;
                 }
diff --git a/dx/src/com/android/dx/command/Main.java b/dx/src/com/android/dx/command/Main.java
index 17038cd..b598d60 100644
--- a/dx/src/com/android/dx/command/Main.java
+++ b/dx/src/com/android/dx/command/Main.java
@@ -31,7 +31,7 @@
         "  [--dump-method=<name>[*]] [--verbose-dump] [--no-files] [--core-library]\n" +
         "  [--num-threads=<n>] [--incremental] [--force-jumbo] [--no-warning]\n" +
         "  [--multi-dex [--main-dex-list=<file> [--minimal-main-dex]]\n" +
-        "  [--input-list=<file>]\n" +
+        "  [--input-list=<file>] [--min-sdk-version=<n>]\n" +
         "  [<file>.class | <file>.{zip,jar,apk} | <directory>] ...\n" +
         "    Convert a set of classfiles into a dex file, optionally embedded in a\n" +
         "    jar/zip. Output name must end with one of: .dex .jar .zip .apk or be a\n" +
@@ -47,6 +47,8 @@
         "    --input-list: <file> is a list of inputs.\n" +
         "    Each line in <file> must end with one of: .class .jar .zip .apk or be a\n" +
         "    directory.\n" +
+        "    --min-sdk-version=<n>: Enable dex file features that require at least sdk\n" +
+        "    version <n>.\n" +
         "  dx --annotool --annotation=<class> [--element=<element types>]\n" +
         "  [--print=<print types>]\n" +
         "  dx --dump [--debug] [--strict] [--bytes] [--optimize]\n" +
diff --git a/dx/src/com/android/dx/command/dexer/Main.java b/dx/src/com/android/dx/command/dexer/Main.java
index 96704df..10189a7 100644
--- a/dx/src/com/android/dx/command/dexer/Main.java
+++ b/dx/src/com/android/dx/command/dexer/Main.java
@@ -1298,6 +1298,9 @@
          */
         public boolean keepClassesInJar = false;
 
+        /** what API level to target */
+        public int minSdkVersion = DexFormat.API_NO_EXTENDED_OPCODES;
+
         /** how much source position info to preserve */
         public int positionInfo = PositionList.LINES;
 
@@ -1556,15 +1559,28 @@
                     maxNumberOfIdxPerDex = Integer.parseInt(parser.getLastValue());
                 } else if(parser.isArg(INPUT_LIST_OPTION + "=")) {
                     File inputListFile = new File(parser.getLastValue());
-                    try{
+                    try {
                         inputList = new ArrayList<String>();
                         readPathsFromFile(inputListFile.getAbsolutePath(), inputList);
-                    } catch(IOException e) {
+                    } catch (IOException e) {
                         context.err.println(
                             "Unable to read input list file: " + inputListFile.getName());
                         // problem reading the file so we should halt execution
                         throw new UsageException();
                     }
+                } else if (parser.isArg("--min-sdk-version=")) {
+                    String arg = parser.getLastValue();
+                    int value;
+                    try {
+                        value = Integer.parseInt(arg);
+                    } catch (NumberFormatException ex) {
+                        value = -1;
+                    }
+                    if (value < 1) {
+                        System.err.println("improper min-sdk-version option: " + arg);
+                        throw new UsageException();
+                    }
+                    minSdkVersion = value;
                 } else {
                     context.err.println("unknown option: " + parser.getCurrent());
                     throw new UsageException();
@@ -1665,6 +1681,7 @@
             }
 
             dexOptions = new DexOptions();
+            dexOptions.minSdkVersion = minSdkVersion;
             dexOptions.forceJumbo = forceJumbo;
         }
     }
diff --git a/dx/src/com/android/dx/dex/DexOptions.java b/dx/src/com/android/dx/dex/DexOptions.java
index fbb7c5a..a54ebd3 100644
--- a/dx/src/com/android/dx/dex/DexOptions.java
+++ b/dx/src/com/android/dx/dex/DexOptions.java
@@ -38,8 +38,8 @@
     */
     public boolean ALIGN_64BIT_REGS_IN_OUTPUT_FINISHER = ALIGN_64BIT_REGS_SUPPORT;
 
-    /** target API level */
-    public int targetApiLevel = DexFormat.API_NO_EXTENDED_OPCODES;
+    /** minimum SDK version targeted */
+    public int minSdkVersion = DexFormat.API_NO_EXTENDED_OPCODES;
 
     /** force generation of jumbo opcodes */
     public boolean forceJumbo = false;
@@ -48,6 +48,14 @@
      * Gets the dex file magic number corresponding to this instance.
      */
     public String getMagic() {
-        return DexFormat.apiToMagic(targetApiLevel);
+        return DexFormat.apiToMagic(minSdkVersion);
+    }
+
+    /**
+     * Returns whether default and static interface methods are allowed.  This became allowed as of
+     * Nougat (SDK version 24).
+     */
+    public boolean canUseDefaultInterfaceMethods() {
+        return minSdkVersion >= DexFormat.API_DEFAULT_INTERFACE_METHODS;
     }
 }