Merge "Add indexOf and lastIndexOf to compile-time string computation"
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index ebaad34..1757459 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -138,6 +138,8 @@
   public final DexString equalsMethodName = createString("equals");
   public final DexString equalsIgnoreCaseMethodName = createString("equalsIgnoreCase");
   public final DexString contentEqualsMethodName = createString("contentEquals");
+  public final DexString indexOfMethodName = createString("indexOf");
+  public final DexString lastIndexOfMethodName = createString("lastIndexOf");
   public final DexString cloneMethodName = createString("clone");
 
   public final DexString valueOfMethodName = createString("valueOf");
@@ -607,6 +609,11 @@
     public final DexMethod equalsIgnoreCase;
     public final DexMethod contentEqualsCharSequence;
 
+    public final DexMethod indexOfInt;
+    public final DexMethod indexOfString;
+    public final DexMethod lastIndexOfInt;
+    public final DexMethod lastIndexOfString;
+
     public final DexMethod valueOf;
     public final DexMethod toString;
 
@@ -619,6 +626,7 @@
       DexString[] needsOneCharSequence = { charSequenceDescriptor };
       DexString[] needsOneString = { stringDescriptor };
       DexString[] needsOneObject = { objectDescriptor };
+      DexString[] needsOneInt = { intDescriptor };
 
       contains = createMethod(
           stringDescriptor, containsMethodName, booleanDescriptor, needsOneCharSequence);
@@ -633,6 +641,15 @@
       contentEqualsCharSequence = createMethod(
           stringDescriptor, contentEqualsMethodName, booleanDescriptor, needsOneCharSequence);
 
+      indexOfString =
+          createMethod(stringDescriptor, indexOfMethodName, intDescriptor, needsOneString);
+      indexOfInt =
+          createMethod(stringDescriptor, indexOfMethodName, intDescriptor, needsOneInt);
+      lastIndexOfString =
+          createMethod(stringDescriptor, lastIndexOfMethodName, intDescriptor, needsOneString);
+      lastIndexOfInt =
+          createMethod(stringDescriptor, lastIndexOfMethodName, intDescriptor, needsOneInt);
+
       valueOf = createMethod(
           stringDescriptor, valueOfMethodName, stringDescriptor, needsOneObject);
       toString = createMethod(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index fd326d9..3fc442a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -59,6 +59,10 @@
   // boolean String#equals(str)
   // boolean String#equalsIgnoreCase(str)
   // boolean String#contentEquals(str)
+  // int String#indexOf(str)
+  // int String#indexOf(int)
+  // int String#lastIndexOf(str)
+  // int String#lastIndexOf(int)
   public void computeTrivialOperationsOnConstString(IRCode code) {
     if (!code.hasConstString) {
       return;
@@ -73,6 +77,7 @@
       DexMethod invokedMethod = invoke.getInvokedMethod();
       Function<String, Integer> operatorWithNoArg = null;
       BiFunction<String, String, Integer> operatorWithString = null;
+      BiFunction<String, Integer, Integer> operatorWithInt = null;
       if (invokedMethod == factory.stringMethods.length) {
         operatorWithNoArg = String::length;
       } else if (invokedMethod == factory.stringMethods.isEmpty) {
@@ -89,8 +94,15 @@
         operatorWithString = (rcv, arg) -> rcv.equalsIgnoreCase(arg) ? 1 : 0;
       } else if (invokedMethod == factory.stringMethods.contentEqualsCharSequence) {
         operatorWithString = (rcv, arg) -> rcv.contentEquals(arg) ? 1 : 0;
-      }
-      if (operatorWithNoArg == null && operatorWithString == null) {
+      } else if (invokedMethod == factory.stringMethods.indexOfInt) {
+        operatorWithInt = String::indexOf;
+      } else if (invokedMethod == factory.stringMethods.indexOfString) {
+        operatorWithString = String::indexOf;
+      } else if (invokedMethod == factory.stringMethods.lastIndexOfInt) {
+        operatorWithInt = String::lastIndexOf;
+      } else if (invokedMethod == factory.stringMethods.lastIndexOfString) {
+        operatorWithString = String::lastIndexOf;
+      } else {
         continue;
       }
       Value rcv = invoke.getReceiver().getAliasedValue();
@@ -99,14 +111,14 @@
           || !rcv.isConstant()) {
         continue;
       }
+
+      ConstNumber constNumber;
       if (operatorWithNoArg != null) {
         assert invoke.inValues().size() == 1;
         ConstString rcvString = rcv.definition.asConstString();
         int v = operatorWithNoArg.apply(rcvString.getValue().toString());
-        ConstNumber constNumber = code.createIntConstant(v);
-        it.replaceCurrentInstruction(constNumber);
-      } else {
-        assert operatorWithString != null;
+        constNumber = code.createIntConstant(v);
+      } else if (operatorWithString != null) {
         assert invoke.inValues().size() == 2;
         Value arg = invoke.inValues().get(1).getAliasedValue();
         if (arg.definition == null
@@ -118,9 +130,24 @@
         ConstString argString = arg.definition.asConstString();
         int v = operatorWithString.apply(
             rcvString.getValue().toString(), argString.getValue().toString());
-        ConstNumber constNumber = code.createIntConstant(v);
-        it.replaceCurrentInstruction(constNumber);
+        constNumber = code.createIntConstant(v);
+      } else {
+        assert operatorWithInt != null;
+        assert invoke.inValues().size() == 2;
+        Value arg = invoke.inValues().get(1).getAliasedValue();
+        if (arg.definition == null
+            || !arg.definition.isConstNumber()
+            || !arg.isConstant()) {
+          continue;
+        }
+        ConstString rcvString = rcv.definition.asConstString();
+        ConstNumber argInt = arg.definition.asConstNumber();
+        int v = operatorWithInt.apply(
+            rcvString.getValue().toString(), argInt.getIntValue());
+        constNumber = code.createIntConstant(v);
       }
+
+      it.replaceCurrentInstruction(constNumber);
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
index 284af11..cd3a131 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
@@ -33,7 +33,9 @@
         && "suffix".endsWith(arg)
         && "CONST".equals(arg)
         && "CONST".equalsIgnoreCase(arg)
-        && "CONST".contentEquals(arg);
+        && "CONST".contentEquals(arg)
+        && "CONST".indexOf(arg) > 0
+        && "CONST".lastIndexOf(arg) > 0;
   }
 
   public static void main(String[] args) {
@@ -46,6 +48,10 @@
       System.out.println(s1.equalsIgnoreCase("PREFIX-const-SUFFIX"));
       System.out.println(s1.contentEquals("prefix-CONST-suffix"));
       System.out.println(s1.contentEquals(new StringBuffer("prefix-CONST-suffix")));
+      System.out.println(s1.indexOf('f'));
+      System.out.println(s1.indexOf("ix"));
+      System.out.println(s1.lastIndexOf('f'));
+      System.out.println(s1.lastIndexOf("ix"));
     }
 
     {
@@ -58,6 +64,10 @@
       System.out.println(s2.equalsIgnoreCase("pre-con-suf"));
       System.out.println(s2.contentEquals("prefix-CONST-suffix"));
       System.out.println(s2.contentEquals(new StringBuffer("prefix-CONST-suffix")));
+      System.out.println(s2.indexOf('f'));
+      System.out.println(s2.indexOf("ix"));
+      System.out.println(s2.lastIndexOf('f'));
+      System.out.println(s2.lastIndexOf("ix"));
     }
 
     {
@@ -94,6 +104,14 @@
       "true",
       // s1, contentEquals(StringBuffer)
       "true",
+      // s1, indexOf(int)
+      "3",
+      // s1, indexOf(String)
+      "4",
+      // s1, lastIndexOf(int)
+      "16",
+      // s1, lastIndexOf(String)
+      "17",
       // s2, contains
       "false",
       // s2, startsWith
@@ -108,6 +126,14 @@
       "false",
       // s2, contentEquals(StringBuffer)
       "false",
+      // s2, indexOf(int)
+      "-1",
+      // s2, indexOf(String)
+      "-1",
+      // s2, lastIndexOf(int)
+      "-1",
+      // s2, lastIndexOf(String)
+      "-1",
       // argCouldBeNull
       "false"
   );
@@ -131,13 +157,16 @@
   private static boolean isStringContentChecker(DexMethod method) {
     return method.getHolder().toDescriptorString().equals("Ljava/lang/String;")
         && method.getArity() == 1
-        && method.proto.returnType.isBooleanType()
+        && (method.proto.returnType.isBooleanType()
+            || method.proto.returnType.isIntType())
         && (method.name.toString().equals("contains")
             || method.name.toString().equals("startsWith")
             || method.name.toString().equals("endsWith")
             || method.name.toString().equals("equals")
             || method.name.toString().equals("equalsIgnoreCase")
-            || method.name.toString().equals("contentEquals"));
+            || method.name.toString().equals("contentEquals")
+            || method.name.toString().equals("indexOf")
+            || method.name.toString().equals("lastIndexOf"));
   }
 
   private long countStringContentChecker(MethodSubject method) {
@@ -161,7 +190,7 @@
         "boolean", "argCouldBeNull", ImmutableList.of("java.lang.String"));
     assertThat(argCouldBeNull, isPresent());
     // Because of nullable argument, all checkers should remain.
-    assertEquals(6, countStringContentChecker(argCouldBeNull));
+    assertEquals(8, countStringContentChecker(argCouldBeNull));
   }
 
   @Test
@@ -173,14 +202,14 @@
         .addProgramClasses(CLASSES)
         .run(MAIN)
         .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, 14);
+    test(result, 22);
 
     result = testForD8()
         .release()
         .addProgramClasses(CLASSES)
         .run(MAIN)
         .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, 8);
+    test(result, 12);
   }
 
   @Test
@@ -192,6 +221,6 @@
         .addKeepMainRule(MAIN)
         .run(MAIN)
         .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, 8);
+    test(result, 12);
   }
 }