/* * Copyright (C) 2015 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. */ import java.lang.reflect.Method; public class Main { public static boolean doThrow = false; public static void assertIntEquals(int expected, int result) { if (expected != result) { throw new Error("Expected: " + expected + ", found: " + result); } } public static void assertBooleanEquals(boolean expected, boolean result) { if (expected != result) { throw new Error("Expected: " + expected + ", found: " + result); } } public static void assertCharEquals(char expected, char result) { if (expected != result) { throw new Error("Expected: " + expected + ", found: " + result); } } public static void assertStringContains(String searchTerm, String result) { if (result == null || !result.contains(searchTerm)) { throw new Error("Search term: " + searchTerm + ", not found in: " + result); } } public static void main(String[] args) { stringEqualsSame(); stringArgumentNotNull("Foo"); assertIntEquals(0, $opt$noinline$getStringLength("")); assertIntEquals(3, $opt$noinline$getStringLength("abc")); assertIntEquals(10, $opt$noinline$getStringLength("0123456789")); assertBooleanEquals(true, $opt$noinline$isStringEmpty("")); assertBooleanEquals(false, $opt$noinline$isStringEmpty("abc")); assertBooleanEquals(false, $opt$noinline$isStringEmpty("0123456789")); assertCharEquals('a', $opt$noinline$stringCharAt("a", 0)); assertCharEquals('a', $opt$noinline$stringCharAt("abc", 0)); assertCharEquals('b', $opt$noinline$stringCharAt("abc", 1)); assertCharEquals('c', $opt$noinline$stringCharAt("abc", 2)); assertCharEquals('7', $opt$noinline$stringCharAt("0123456789", 7)); try { $opt$noinline$stringCharAt("abc", -1); throw new Error("Should throw SIOOB."); } catch (StringIndexOutOfBoundsException sioob) { assertStringContains("java.lang.String.charAt", sioob.getStackTrace()[0].toString()); assertStringContains("Main.$opt$noinline$stringCharAt", sioob.getStackTrace()[1].toString()); } try { $opt$noinline$stringCharAt("abc", 3); throw new Error("Should throw SIOOB."); } catch (StringIndexOutOfBoundsException sioob) { assertStringContains("java.lang.String.charAt", sioob.getStackTrace()[0].toString()); assertStringContains("Main.$opt$noinline$stringCharAt", sioob.getStackTrace()[1].toString()); } try { $opt$noinline$stringCharAt("abc", Integer.MAX_VALUE); throw new Error("Should throw SIOOB."); } catch (StringIndexOutOfBoundsException sioob) { assertStringContains("java.lang.String.charAt", sioob.getStackTrace()[0].toString()); assertStringContains("Main.$opt$noinline$stringCharAt", sioob.getStackTrace()[1].toString()); } assertCharEquals('7', $opt$noinline$stringCharAtCatch("0123456789", 7)); assertCharEquals('7', $noinline$runSmaliTest("stringCharAtCatch", "0123456789", 7)); assertCharEquals('\0', $opt$noinline$stringCharAtCatch("0123456789", 10)); assertCharEquals('\0', $noinline$runSmaliTest("stringCharAtCatch","0123456789", 10)); assertIntEquals('a' + 'b' + 'c', $opt$noinline$stringSumChars("abc")); assertIntEquals('a' + 'b' + 'c', $opt$noinline$stringSumLeadingChars("abcdef", 3)); try { $opt$noinline$stringSumLeadingChars("abcdef", 7); throw new Error("Should throw SIOOB."); } catch (StringIndexOutOfBoundsException sioob) { assertStringContains("java.lang.String.charAt", sioob.getStackTrace()[0].toString()); assertStringContains("Main.$opt$noinline$stringSumLeadingChars", sioob.getStackTrace()[1].toString()); } assertIntEquals('a' + 'b' + 'c' + 'd', $opt$noinline$stringSum4LeadingChars("abcdef")); try { $opt$noinline$stringSum4LeadingChars("abc"); throw new Error("Should throw SIOOB."); } catch (StringIndexOutOfBoundsException sioob) { assertStringContains("java.lang.String.charAt", sioob.getStackTrace()[0].toString()); assertStringContains("Main.$opt$noinline$stringSum4LeadingChars", sioob.getStackTrace()[1].toString()); } } /// CHECK-START: int Main.$opt$noinline$getStringLength(java.lang.String) instruction_simplifier (before) /// CHECK-DAG: <> InvokeVirtual intrinsic:StringLength /// CHECK-DAG: Return [<>] /// CHECK-START: int Main.$opt$noinline$getStringLength(java.lang.String) instruction_simplifier (after) /// CHECK-DAG: <> ParameterValue /// CHECK-DAG: <> NullCheck [<>] /// CHECK-DAG: <> ArrayLength [<>] is_string_length:true /// CHECK-DAG: Return [<>] /// CHECK-START: int Main.$opt$noinline$getStringLength(java.lang.String) instruction_simplifier (after) /// CHECK-NOT: InvokeVirtual intrinsic:StringLength static public int $opt$noinline$getStringLength(String s) { if (doThrow) { throw new Error(); } return s.length(); } /// CHECK-START: boolean Main.$opt$noinline$isStringEmpty(java.lang.String) instruction_simplifier (before) /// CHECK-DAG: <> InvokeVirtual intrinsic:StringIsEmpty /// CHECK-DAG: Return [<>] /// CHECK-START: boolean Main.$opt$noinline$isStringEmpty(java.lang.String) instruction_simplifier (after) /// CHECK-DAG: <> ParameterValue /// CHECK-DAG: <> IntConstant 0 /// CHECK-DAG: <> NullCheck [<>] /// CHECK-DAG: <> ArrayLength [<>] is_string_length:true /// CHECK-DAG: <> Equal [<>,<>] /// CHECK-DAG: Return [<>] /// CHECK-START: boolean Main.$opt$noinline$isStringEmpty(java.lang.String) instruction_simplifier (after) /// CHECK-NOT: InvokeVirtual intrinsic:StringIsEmpty static public boolean $opt$noinline$isStringEmpty(String s) { if (doThrow) { throw new Error(); } return s.isEmpty(); } /// CHECK-START: char Main.$opt$noinline$stringCharAt(java.lang.String, int) instruction_simplifier (before) /// CHECK-DAG: <> InvokeVirtual intrinsic:StringCharAt /// CHECK-DAG: Return [<>] /// CHECK-START: char Main.$opt$noinline$stringCharAt(java.lang.String, int) instruction_simplifier (after) /// CHECK-DAG: <> ParameterValue /// CHECK-DAG: <> ParameterValue /// CHECK-DAG: <> NullCheck [<>] /// CHECK-DAG: <> ArrayLength [<>] is_string_length:true /// CHECK-DAG: <> BoundsCheck [<>,<>] is_string_char_at:true /// CHECK-DAG: <> ArrayGet [<>,<>] is_string_char_at:true /// CHECK-DAG: Return [<>] /// CHECK-START: char Main.$opt$noinline$stringCharAt(java.lang.String, int) instruction_simplifier (after) /// CHECK-NOT: InvokeVirtual intrinsic:StringCharAt static public char $opt$noinline$stringCharAt(String s, int pos) { if (doThrow) { throw new Error(); } return s.charAt(pos); } /// CHECK-START: char Main.$opt$noinline$stringCharAtCatch(java.lang.String, int) instruction_simplifier (before) /// CHECK-DAG: <> IntConstant 0 /// CHECK-DAG: <> InvokeVirtual intrinsic:StringCharAt // The return value can come from a Phi should the two returns be merged. // Please refer to the Smali code for a more detailed verification. /// CHECK-DAG: Return [{{(c|i)\d+}}] /// CHECK-START: char Main.$opt$noinline$stringCharAtCatch(java.lang.String, int) instruction_simplifier (after) /// CHECK-DAG: <> ParameterValue /// CHECK-DAG: <> ParameterValue /// CHECK-DAG: <> IntConstant 0 /// CHECK-DAG: <> NullCheck [<>] /// CHECK-DAG: <> ArrayLength [<>] is_string_length:true /// CHECK-DAG: <> BoundsCheck [<>,<>] is_string_char_at:true /// CHECK-DAG: <> ArrayGet [<>,<>] is_string_char_at:true /// CHECK-DAG: Return [{{(c|i)\d+}}] /// CHECK-START: char Main.$opt$noinline$stringCharAtCatch(java.lang.String, int) instruction_simplifier (after) /// CHECK-NOT: InvokeVirtual intrinsic:StringCharAt static public char $opt$noinline$stringCharAtCatch(String s, int pos) { if (doThrow) { throw new Error(); } try { return s.charAt(pos); } catch (StringIndexOutOfBoundsException ignored) { return '\0'; } } /// CHECK-START: int Main.$opt$noinline$stringSumChars(java.lang.String) instruction_simplifier (before) /// CHECK-DAG: InvokeVirtual intrinsic:StringLength /// CHECK-DAG: InvokeVirtual intrinsic:StringCharAt /// CHECK-START: int Main.$opt$noinline$stringSumChars(java.lang.String) instruction_simplifier (after) /// CHECK-DAG: ArrayLength is_string_length:true /// CHECK-DAG: ArrayLength is_string_length:true /// CHECK-DAG: BoundsCheck is_string_char_at:true /// CHECK-DAG: ArrayGet is_string_char_at:true /// CHECK-START: int Main.$opt$noinline$stringSumChars(java.lang.String) instruction_simplifier (after) /// CHECK-NOT: InvokeVirtual intrinsic:StringLength /// CHECK-NOT: InvokeVirtual intrinsic:StringCharAt /// CHECK-START: int Main.$opt$noinline$stringSumChars(java.lang.String) GVN (after) /// CHECK-DAG: ArrayLength is_string_length:true /// CHECK-NOT: ArrayLength is_string_length:true /// CHECK-START: int Main.$opt$noinline$stringSumChars(java.lang.String) BCE (after) /// CHECK-NOT: BoundsCheck static public int $opt$noinline$stringSumChars(String s) { if (doThrow) { throw new Error(); } int sum = 0; int len = s.length(); for (int i = 0; i < len; ++i) { sum += s.charAt(i); } return sum; } /// CHECK-START: int Main.$opt$noinline$stringSumLeadingChars(java.lang.String, int) instruction_simplifier (before) /// CHECK-DAG: InvokeVirtual intrinsic:StringCharAt /// CHECK-START: int Main.$opt$noinline$stringSumLeadingChars(java.lang.String, int) instruction_simplifier (after) /// CHECK-DAG: ArrayLength is_string_length:true /// CHECK-DAG: BoundsCheck is_string_char_at:true /// CHECK-DAG: ArrayGet is_string_char_at:true /// CHECK-START: int Main.$opt$noinline$stringSumLeadingChars(java.lang.String, int) instruction_simplifier (after) /// CHECK-NOT: InvokeVirtual intrinsic:StringCharAt /// CHECK-START: int Main.$opt$noinline$stringSumLeadingChars(java.lang.String, int) BCE (after) /// CHECK-DAG: Deoptimize env:[[{{[^\]]*}}]] /// CHECK-START: int Main.$opt$noinline$stringSumLeadingChars(java.lang.String, int) BCE (after) /// CHECK-NOT: BoundsCheck is_string_char_at:true static public int $opt$noinline$stringSumLeadingChars(String s, int n) { if (doThrow) { throw new Error(); } int sum = 0; for (int i = 0; i < n; ++i) { sum += s.charAt(i); } return sum; } /// CHECK-START: int Main.$opt$noinline$stringSum4LeadingChars(java.lang.String) instruction_simplifier (before) /// CHECK-DAG: InvokeVirtual intrinsic:StringCharAt /// CHECK-DAG: InvokeVirtual intrinsic:StringCharAt /// CHECK-DAG: InvokeVirtual intrinsic:StringCharAt /// CHECK-DAG: InvokeVirtual intrinsic:StringCharAt /// CHECK-START: int Main.$opt$noinline$stringSum4LeadingChars(java.lang.String) instruction_simplifier (after) /// CHECK-DAG: ArrayLength is_string_length:true /// CHECK-DAG: BoundsCheck is_string_char_at:true /// CHECK-DAG: ArrayGet is_string_char_at:true /// CHECK-DAG: ArrayLength is_string_length:true /// CHECK-DAG: BoundsCheck is_string_char_at:true /// CHECK-DAG: ArrayGet is_string_char_at:true /// CHECK-DAG: ArrayLength is_string_length:true /// CHECK-DAG: BoundsCheck is_string_char_at:true /// CHECK-DAG: ArrayGet is_string_char_at:true /// CHECK-DAG: ArrayLength is_string_length:true /// CHECK-DAG: BoundsCheck is_string_char_at:true /// CHECK-DAG: ArrayGet is_string_char_at:true /// CHECK-START: int Main.$opt$noinline$stringSum4LeadingChars(java.lang.String) instruction_simplifier (after) /// CHECK-NOT: InvokeVirtual intrinsic:StringCharAt /// CHECK-START: int Main.$opt$noinline$stringSum4LeadingChars(java.lang.String) BCE (after) /// CHECK-DAG: Deoptimize env:[[{{[^\]]*}}]] /// CHECK-START: int Main.$opt$noinline$stringSum4LeadingChars(java.lang.String) BCE (after) /// CHECK-NOT: BoundsCheck is_string_char_at:true static public int $opt$noinline$stringSum4LeadingChars(String s) { if (doThrow) { throw new Error(); } int sum = s.charAt(0) + s.charAt(1) + s.charAt(2) + s.charAt(3); return sum; } /// CHECK-START: boolean Main.stringEqualsSame() instruction_simplifier (before) /// CHECK: InvokeStaticOrDirect /// CHECK-START: boolean Main.stringEqualsSame() register (before) /// CHECK: <> IntConstant 1 /// CHECK: Return [<>] /// CHECK-START: boolean Main.stringEqualsSame() register (before) /// CHECK-NOT: InvokeStaticOrDirect public static boolean stringEqualsSame() { return $inline$callStringEquals("obj", "obj"); } /// CHECK-START: boolean Main.stringEqualsNull() register (after) /// CHECK: <> InvokeVirtual /// CHECK: Return [<>] public static boolean stringEqualsNull() { String o = (String)myObject; return $inline$callStringEquals(o, o); } public static boolean $inline$callStringEquals(String a, String b) { return a.equals(b); } /// CHECK-START-X86: boolean Main.stringArgumentNotNull(java.lang.Object) disassembly (after) /// CHECK: InvokeVirtual {{.*\.equals.*}} intrinsic:StringEquals /// CHECK-NOT: test /// CHECK-START-X86_64: boolean Main.stringArgumentNotNull(java.lang.Object) disassembly (after) /// CHECK: InvokeVirtual {{.*\.equals.*}} intrinsic:StringEquals /// CHECK-NOT: test /// CHECK-START-ARM: boolean Main.stringArgumentNotNull(java.lang.Object) disassembly (after) /// CHECK: InvokeVirtual {{.*\.equals.*}} intrinsic:StringEquals // CompareAndBranchIfZero() may emit either CBZ or CMP+BEQ. /// CHECK-NOT: cbz /// CHECK-NOT: cmp {{r\d+}}, #0 // Terminate the scope for the CHECK-NOT search at the reference or length comparison, // whichever comes first. /// CHECK: cmp {{r\d+}}, {{r\d+}} /// CHECK-START-ARM64: boolean Main.stringArgumentNotNull(java.lang.Object) disassembly (after) /// CHECK: InvokeVirtual {{.*\.equals.*}} intrinsic:StringEquals /// CHECK-NOT: cbz // Terminate the scope for the CHECK-NOT search at the reference or length comparison, // whichever comes first. /// CHECK: cmp {{w.*,}} {{w.*|#.*}} /// CHECK-START-MIPS: boolean Main.stringArgumentNotNull(java.lang.Object) disassembly (after) /// CHECK: InvokeVirtual {{.*\.equals.*}} intrinsic:StringEquals /// CHECK-NOT: beq zero, /// CHECK-NOT: beqz /// CHECK-NOT: beqzc // Terminate the scope for the CHECK-NOT search at the class field or length comparison, // whichever comes first. /// CHECK: lw /// CHECK-START-MIPS64: boolean Main.stringArgumentNotNull(java.lang.Object) disassembly (after) /// CHECK: InvokeVirtual {{.*\.equals.*}} intrinsic:StringEquals /// CHECK-NOT: beqzc // Terminate the scope for the CHECK-NOT search at the reference comparison. /// CHECK: beqc public static boolean stringArgumentNotNull(Object obj) { obj.getClass(); return "foo".equals(obj); } // Test is very brittle as it depends on the order we emit instructions. /// CHECK-START-X86: boolean Main.stringArgumentIsString() disassembly (after) /// CHECK: InvokeVirtual intrinsic:StringEquals /// CHECK: test /// CHECK: jz/eq // Check that we don't try to compare the classes. /// CHECK-NOT: mov /// CHECK: cmp // Test is very brittle as it depends on the order we emit instructions. /// CHECK-START-X86_64: boolean Main.stringArgumentIsString() disassembly (after) /// CHECK: InvokeVirtual intrinsic:StringEquals /// CHECK: test /// CHECK: jz/eq // Check that we don't try to compare the classes. /// CHECK-NOT: mov /// CHECK: cmp // Test is brittle as it depends on the class offset being 0. /// CHECK-START-ARM: boolean Main.stringArgumentIsString() disassembly (after) /// CHECK: InvokeVirtual intrinsic:StringEquals /// CHECK: {{cbz|cmp}} // Check that we don't try to compare the classes. // The dissassembler currently explicitly emits the offset 0 but don't rely on it. // We want to terminate the CHECK-NOT search after two CMPs, one for reference // equality and one for length comparison but these may be emitted in different order, // so repeat the check twice. /// CHECK-NOT: ldr{{(|.w)}} {{r\d+}}, [{{r\d+}}] /// CHECK-NOT: ldr{{(|.w)}} {{r\d+}}, [{{r\d+}}, #0] /// CHECK: cmp {{r\d+}}, {{r\d+}} /// CHECK-NOT: ldr{{(|.w)}} {{r\d+}}, [{{r\d+}}] /// CHECK-NOT: ldr{{(|.w)}} {{r\d+}}, [{{r\d+}}, #0] /// CHECK: cmp {{r\d+}}, {{r\d+}} // Test is brittle as it depends on the class offset being 0. /// CHECK-START-ARM64: boolean Main.stringArgumentIsString() disassembly (after) /// CHECK: InvokeVirtual intrinsic:StringEquals /// CHECK: cbz // Check that we don't try to compare the classes. // The dissassembler currently does not explicitly emits the offset 0 but don't rely on it. // We want to terminate the CHECK-NOT search after two CMPs, one for reference // equality and one for length comparison but these may be emitted in different order, // so repeat the check twice. /// CHECK-NOT: ldr {{w\d+}}, [{{x\d+}}] /// CHECK-NOT: ldr {{w\d+}}, [{{x\d+}}, #0] /// CHECK: cmp {{w\d+}}, {{w\d+|#.*}} /// CHECK-NOT: ldr {{w\d+}}, [{{x\d+}}] /// CHECK-NOT: ldr {{w\d+}}, [{{x\d+}}, #0] /// CHECK: cmp {{w\d+}}, {{w\d+|#.*}} // Test is brittle as it depends on the class offset being 0. /// CHECK-START-MIPS: boolean Main.stringArgumentIsString() disassembly (after) /// CHECK: InvokeVirtual intrinsic:StringEquals /// CHECK: beq{{(zc)?}} // Check that we don't try to compare the classes. /// CHECK-NOT: lw {{r\d+}}, +0({{r\d+}}) /// CHECK: bne{{c?}} // Test is brittle as it depends on the class offset being 0. /// CHECK-START-MIPS64: boolean Main.stringArgumentIsString() disassembly (after) /// CHECK: InvokeVirtual intrinsic:StringEquals /// CHECK: beqzc // Check that we don't try to compare the classes. /// CHECK-NOT: lw {{r\d+}}, +0({{r\d+}}) /// CHECK: bnec public static boolean stringArgumentIsString() { return "foo".equals(myString); } static String myString; static Object myObject; public static char $noinline$runSmaliTest(String name, String str, int pos) { try { Class c = Class.forName("SmaliTests"); Method m = c.getMethod(name, String.class, int.class); return (Character) m.invoke(null, str, pos); } catch (Exception ex) { throw new Error(ex); } } }