1 /*
2  * Copyright 2014, Google Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following disclaimer
13  * in the documentation and/or other materials provided with the
14  * distribution.
15  *     * Neither the name of Google Inc. nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 package org.jf.smalidea;
33 
34 import com.intellij.lexer.Lexer;
35 import com.intellij.testFramework.LexerTestCase;
36 
37 import java.util.Random;
38 
39 /**
40  * This is mostly just a smoke test to make sure the lexer is working. The lexer itself has its
41  * own tests in the smali module
42  */
43 public class SmaliLexerTest extends LexerTestCase {
testHelloWorld()44     public void testHelloWorld() {
45         String text =
46                 ".class public LHelloWorld;\n" +
47                 ".super Ljava/lang/Object;\n" +
48                 ".method public static main([Ljava/lang/String;)V\n" +
49                 "    .registers 2\n" +
50                 "    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;\n" +
51                 "    const-string v1, \"Hello World!\"\n" +
52                 "    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V\n" +
53                 "    return-void\n" +
54                 ".end method";
55 
56         doTest(text,
57                 "CLASS_DIRECTIVE ('.class')\n" +
58                 "WHITE_SPACE (' ')\n" +
59                 "ACCESS_SPEC ('public')\n" +
60                 "WHITE_SPACE (' ')\n" +
61                 "CLASS_DESCRIPTOR ('LHelloWorld;')\n" +
62                 "WHITE_SPACE ('\\n')\n" +
63                 "SUPER_DIRECTIVE ('.super')\n" +
64                 "WHITE_SPACE (' ')\n" +
65                 "CLASS_DESCRIPTOR ('Ljava/lang/Object;')\n" +
66                 "WHITE_SPACE ('\\n')\n" +
67                 "METHOD_DIRECTIVE ('.method')\n" +
68                 "WHITE_SPACE (' ')\n" +
69                 "ACCESS_SPEC ('public')\n" +
70                 "WHITE_SPACE (' ')\n" +
71                 "ACCESS_SPEC ('static')\n" +
72                 "WHITE_SPACE (' ')\n" +
73                 "SIMPLE_NAME ('main')\n" +
74                 "OPEN_PAREN ('(')\n" +
75                 "ARRAY_TYPE_PREFIX ('[')\n" +
76                 "CLASS_DESCRIPTOR ('Ljava/lang/String;')\n" +
77                 "CLOSE_PAREN (')')\n" +
78                 "VOID_TYPE ('V')\n" +
79                 "WHITE_SPACE ('\\n    ')\n" +
80                 "REGISTERS_DIRECTIVE ('.registers')\n" +
81                 "WHITE_SPACE (' ')\n" +
82                 "POSITIVE_INTEGER_LITERAL ('2')\n" +
83                 "WHITE_SPACE ('\\n    ')\n" +
84                 "INSTRUCTION_FORMAT21c_FIELD ('sget-object')\n" +
85                 "WHITE_SPACE (' ')\n" +
86                 "REGISTER ('v0')\n" +
87                 "COMMA (',')\n" +
88                 "WHITE_SPACE (' ')\n" +
89                 "CLASS_DESCRIPTOR ('Ljava/lang/System;')\n" +
90                 "ARROW ('->')\n" +
91                 "SIMPLE_NAME ('out')\n" +
92                 "COLON (':')\n" +
93                 "CLASS_DESCRIPTOR ('Ljava/io/PrintStream;')\n" +
94                 "WHITE_SPACE ('\\n    ')\n" +
95                 "INSTRUCTION_FORMAT21c_STRING ('const-string')\n" +
96                 "WHITE_SPACE (' ')\n" +
97                 "REGISTER ('v1')\n" +
98                 "COMMA (',')\n" +
99                 "WHITE_SPACE (' ')\n" +
100                 "STRING_LITERAL ('\"Hello World!\"')\n" +
101                 "WHITE_SPACE ('\\n    ')\n" +
102                 "INSTRUCTION_FORMAT35c_METHOD ('invoke-virtual')\n" +
103                 "WHITE_SPACE (' ')\n" +
104                 "OPEN_BRACE ('{')\n" +
105                 "REGISTER ('v0')\n" +
106                 "COMMA (',')\n" +
107                 "WHITE_SPACE (' ')\n" +
108                 "REGISTER ('v1')\n" +
109                 "CLOSE_BRACE ('}')\n" +
110                 "COMMA (',')\n" +
111                 "WHITE_SPACE (' ')\n" +
112                 "CLASS_DESCRIPTOR ('Ljava/io/PrintStream;')\n" +
113                 "ARROW ('->')\n" +
114                 "SIMPLE_NAME ('println')\n" +
115                 "OPEN_PAREN ('(')\n" +
116                 "CLASS_DESCRIPTOR ('Ljava/lang/String;')\n" +
117                 "CLOSE_PAREN (')')\n" +
118                 "VOID_TYPE ('V')\n" +
119                 "WHITE_SPACE ('\\n    ')\n" +
120                 "INSTRUCTION_FORMAT10x ('return-void')\n" +
121                 "WHITE_SPACE ('\\n')\n" +
122                 "END_METHOD_DIRECTIVE ('.end method')"
123         );
124     }
125 
createLexer()126     @Override protected Lexer createLexer() {
127         return new SmaliLexer();
128     }
129 
getDirPath()130     @Override protected String getDirPath() {
131         return "";
132     }
133 
testErrorToken()134     public void testErrorToken() {
135         String text = ".class public .blah";
136         doTest(text,
137                 "CLASS_DIRECTIVE ('.class')\n" +
138                         "WHITE_SPACE (' ')\n" +
139                         "ACCESS_SPEC ('public')\n" +
140                         "WHITE_SPACE (' ')\n" +
141                         "BAD_CHARACTER ('.blah')\n");
142     }
143 
144     /**
145      * Type out an example smali file character by character, ensuring that no exceptions are thrown
146      */
testPartialText()147     public void testPartialText() {
148         String text =
149                 ".class public LHelloWorld;\n" +
150                         ".super Ljava/lang/Object;\n" +
151                         ".method public static main([Ljava/lang/String;)V\n" +
152                         "    .registers 2\n" +
153                         "    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;\n" +
154                         "    const-string v1, \"Hello World!\"\n" +
155                         "    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V\n" +
156                         "    return-void\n" +
157                         ".end method";
158 
159         for (int i=1; i<text.length(); i++) {
160             printTokens(text.substring(i), 0);
161         }
162     }
163 
164     /**
165      * Generate some random text and make sure the lexer doesn't throw any exceptions
166      */
testRandomText()167     public void testRandomText() {
168         for (int i=0; i<100; i++) {
169             String randomString = randomString(1000);
170 
171             printTokens(randomString, 0);
172         }
173     }
174 
175     private Random random = new Random(123456789);
randomString(int length)176     private String randomString(int length) {
177         StringBuilder sb = new StringBuilder();
178         for (int i=0; i<length; i++) {
179             int type = random.nextInt(10);
180 
181             if (type == 9) {
182                 int randomCodepoint;
183                 do {
184                     randomCodepoint = random.nextInt();
185                 } while(!Character.isValidCodePoint(randomCodepoint));
186                 sb.appendCodePoint(randomCodepoint);
187             } else if (type == 8) {
188                 char randomChar;
189                 do {
190                     randomChar = (char)random.nextInt(2^16);
191                 } while(!Character.isValidCodePoint(randomChar));
192                 sb.append(randomChar);
193             } else if (type > 4) {
194                 sb.append((char)random.nextInt(256));
195             } else if (type == 4) {
196                 sb.append(' ');
197             } else {
198                 sb.append((char)random.nextInt(128));
199             }
200         }
201 
202         return sb.toString();
203     }
204 }
205