1 /* 2 * Copyright 2016, Google LLC 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * Neither the name of Google LLC nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 package com.android.tools.smali.smali; 32 33 import org.antlr.runtime.ANTLRInputStream; 34 import org.antlr.runtime.CommonToken; 35 import org.antlr.runtime.CommonTokenStream; 36 import org.antlr.runtime.RecognitionException; 37 import org.junit.Assert; 38 import org.junit.Test; 39 40 import java.io.File; 41 import java.io.IOException; 42 import java.io.InputStream; 43 import java.io.InputStreamReader; 44 import java.util.HashMap; 45 import java.util.List; 46 47 import static com.android.tools.smali.smali.expectedTokensTestGrammarParser.ExpectedToken; 48 49 public class LexerTest { 50 private static final HashMap<String, Integer> tokenTypesByName; 51 private static final int MOST_RECENT_API = 10000; 52 53 static { 54 tokenTypesByName = new HashMap<String, Integer>(); 55 56 for (int i=0; i<smaliParser.tokenNames.length; i++) { tokenTypesByName.put(smaliParser.tokenNames[i], i)57 tokenTypesByName.put(smaliParser.tokenNames[i], i); 58 } 59 } 60 61 @Test DirectiveTest()62 public void DirectiveTest() { 63 runTest("DirectiveTest"); 64 } 65 66 @Test ByteLiteralTest()67 public void ByteLiteralTest() { 68 runTest("ByteLiteralTest"); 69 } 70 71 @Test ShortLiteralTest()72 public void ShortLiteralTest() { 73 runTest("ShortLiteralTest"); 74 } 75 76 @Test IntegerLiteralTest()77 public void IntegerLiteralTest() { 78 runTest("IntegerLiteralTest"); 79 } 80 81 @Test LongLiteralTest()82 public void LongLiteralTest() { 83 runTest("LongLiteralTest"); 84 } 85 86 @Test FloatLiteralTest()87 public void FloatLiteralTest() { 88 runTest("FloatLiteralTest"); 89 } 90 91 @Test CharLiteralTest()92 public void CharLiteralTest() { 93 runTest("CharLiteralTest"); 94 } 95 96 @Test StringLiteralTest()97 public void StringLiteralTest() { 98 runTest("StringLiteralTest"); 99 } 100 101 @Test MiscTest()102 public void MiscTest() { 103 runTest("MiscTest"); 104 } 105 106 @Test CommentTest()107 public void CommentTest() { 108 runTest("CommentTest", false); 109 } 110 111 @Test InstructionTest()112 public void InstructionTest() { 113 runTest("InstructionTest", true); 114 } 115 116 @Test TypeAndIdentifierTest()117 public void TypeAndIdentifierTest() { 118 runTest("TypeAndIdentifierTest"); 119 } 120 121 @Test TypeAndIdentifierTest_api29()122 public void TypeAndIdentifierTest_api29() { 123 runTest("TypeAndIdentifierTest_api29", 29); 124 } 125 126 @Test SymbolTest()127 public void SymbolTest() { 128 runTest("SymbolTest", false); 129 } 130 131 @Test RealSmaliFileTest()132 public void RealSmaliFileTest() { 133 runTest("RealSmaliFileTest", true); 134 } 135 runTest(String test)136 public void runTest(String test) { 137 runTest(test, true, MOST_RECENT_API); 138 } 139 runTest(String test, boolean discardHiddenTokens)140 public void runTest(String test, boolean discardHiddenTokens) { 141 runTest(test, discardHiddenTokens, MOST_RECENT_API); 142 } 143 runTest(String test, int apiLevel)144 public void runTest(String test, int apiLevel) { 145 runTest(test, true, apiLevel); 146 } 147 runTest(String test, boolean discardHiddenTokens, int apiLevel)148 public void runTest(String test, boolean discardHiddenTokens, int apiLevel) { 149 String smaliFile = String.format("LexerTest%s%s.smali", File.separatorChar, test); 150 String tokensFile = String.format("LexerTest%s%s.tokens", File.separatorChar, test); 151 152 com.android.tools.smali.smali.expectedTokensTestGrammarLexer expectedTokensLexer = null; 153 try { 154 expectedTokensLexer = new com.android.tools.smali.smali.expectedTokensTestGrammarLexer(new ANTLRInputStream( 155 LexerTest.class.getClassLoader().getResourceAsStream(tokensFile))); 156 } catch (IOException ex) { 157 throw new RuntimeException(ex); 158 } 159 160 CommonTokenStream expectedTokensStream = new CommonTokenStream(expectedTokensLexer); 161 162 com.android.tools.smali.smali.expectedTokensTestGrammarParser expectedTokensParser = 163 new com.android.tools.smali.smali.expectedTokensTestGrammarParser(expectedTokensStream); 164 try { 165 expectedTokensParser.top(); 166 } catch (RecognitionException ex) { 167 throw new RuntimeException(ex); 168 } 169 170 List<ExpectedToken> expectedTokens = expectedTokensParser.getExpectedTokens(); 171 172 InputStream smaliStream = LexerTest.class.getClassLoader().getResourceAsStream(smaliFile); 173 if (smaliStream == null) { 174 Assert.fail("Could not load " + smaliFile); 175 } 176 smaliFlexLexer lexer = new smaliFlexLexer(new InputStreamReader(smaliStream), 177 apiLevel); 178 lexer.setSourceFile(new File(test + ".smali")); 179 lexer.setSuppressErrors(true); 180 181 CommonTokenStream tokenStream = new CommonTokenStream(lexer); 182 tokenStream.fill(); 183 List tokens = tokenStream.getTokens(); 184 185 int expectedTokenIndex = 0; 186 CommonToken token; 187 for (int i=0; i<tokens.size()-1; i++) { 188 token = (CommonToken)tokens.get(i); 189 190 if (discardHiddenTokens && token.getChannel() == smaliParser.HIDDEN) { 191 continue; 192 } 193 194 if (expectedTokenIndex >= expectedTokens.size()) { 195 Assert.fail("Too many tokens"); 196 } 197 198 if (token.getType() == smaliParser.INVALID_TOKEN) { 199 Assert.assertTrue("Encountered an INVALID_TOKEN not on the error channel", 200 token.getChannel() == smaliParser.ERROR_CHANNEL); 201 } 202 203 ExpectedToken expectedToken = expectedTokens.get(expectedTokenIndex++); 204 if (!tokenTypesByName.containsKey(expectedToken.tokenName)) { 205 Assert.fail("Unknown token: " + expectedToken.tokenName); 206 } 207 int expectedTokenType = tokenTypesByName.get(expectedToken.tokenName); 208 209 if (token.getType() != expectedTokenType) { 210 Assert.fail(String.format("Invalid token at index %d. Expecting %s, got %s(%s)", 211 expectedTokenIndex-1, expectedToken.tokenName, getTokenName(token.getType()), token.getText())); 212 } 213 214 if (expectedToken.tokenText != null) { 215 if (!expectedToken.tokenText.equals(token.getText())) { 216 Assert.fail( 217 String.format("Invalid token text at index %d. Expecting text \"%s\", got \"%s\"", 218 expectedTokenIndex - 1, expectedToken.tokenText, token.getText())); 219 } 220 } 221 } 222 223 if (expectedTokenIndex < expectedTokens.size()) { 224 Assert.fail(String.format("Not enough tokens. Expecting %d tokens, but got %d", expectedTokens.size(), 225 expectedTokenIndex)); 226 } 227 } 228 229 230 getTokenName(int tokenType)231 private static String getTokenName(int tokenType) { 232 return smaliParser.tokenNames[tokenType]; 233 } 234 } 235