1 /* 2 * Copyright 2016, 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.google.common.collect.Sets; 35 import com.intellij.codeInsight.CodeInsightTestCase; 36 import com.intellij.codeInsight.completion.CodeCompletionHandlerBase; 37 import com.intellij.codeInsight.completion.CompletionType; 38 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; 39 import com.intellij.codeInsight.lookup.LookupElement; 40 import com.intellij.codeInsight.lookup.LookupManager; 41 import com.intellij.debugger.NoDataException; 42 import com.intellij.debugger.engine.evaluation.CodeFragmentKind; 43 import com.intellij.debugger.engine.evaluation.TextWithImportsImpl; 44 import com.intellij.openapi.editor.Editor; 45 import com.intellij.openapi.editor.impl.EditorImpl; 46 import com.intellij.openapi.fileEditor.FileEditorManager; 47 import com.intellij.openapi.fileEditor.OpenFileDescriptor; 48 import com.intellij.openapi.projectRoots.Sdk; 49 import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl; 50 import com.intellij.openapi.vfs.VirtualFile; 51 import com.intellij.psi.JavaCodeFragment; 52 import com.intellij.psi.PsiDocumentManager; 53 import com.intellij.psi.PsiElement; 54 import com.intellij.psi.impl.source.tree.java.PsiReferenceExpressionImpl; 55 import org.jetbrains.annotations.NotNull; 56 import org.jf.smalidea.debugging.SmaliCodeFragmentFactory; 57 import org.jf.smalidea.psi.impl.SmaliFile; 58 import org.junit.Assert; 59 60 import java.util.HashSet; 61 import java.util.List; 62 63 public class SmaliCodeFragmentFactoryTest extends CodeInsightTestCase { 64 private static final String completionTestClass = 65 ".class public Lmy/pkg/blah; .super Ljava/lang/Object;\n" + 66 ".method public getRandomParentType(I)I\n" + 67 " .registers 4\n" + 68 " .param p1, \"edge\" # I\n" + 69 "\n" + 70 " .prologue\n" + 71 " const/4 v1, 0x2\n" + // 0 72 "\n" + 73 " .line 179\n" + 74 " if-nez p1, :cond_5\n" + 75 "\n" + 76 " move v0, v1\n" + // 2 77 "\n" + 78 " .line 185\n" + 79 " :goto_4\n" + 80 " return v0\n" + 81 "\n" + 82 " .line 182\n" + 83 " :cond_5\n" + 84 " if-ne p1, v1, :cond_f\n" + 85 "\n" + 86 " .line 183\n" + 87 " sget-object v0, Lorg/jf/Penroser/PenroserApp;->random:Ljava/util/Random;\n" + 88 "\n" + 89 " const/4 v1, 0x3\n" + // 6 90 "\n" + 91 " invoke-virtual {v0, v1}, Ljava/util/Random;->nextInt(I)I\n" + 92 "\n" + 93 " move-result v0\n" + 94 "\n" + 95 " goto :goto_4\n" + 96 "\n" + 97 " .line 185\n" + 98 " :cond_f\n" + 99 " sget-object v0, Lorg/jf/Penroser/PenroserApp;->random:Ljava/util/Random;\n" + 100 "\n" + 101 " invoke-virtual {v0, v1}, Ljava/util/Random;->nextInt(I)I\n" + 102 "\n" + 103 " move-result v0\n" + 104 "\n" + 105 " goto :goto_4\n" + 106 ".end method"; 107 108 public void testCompletion() throws NoDataException { 109 SmaliFile smaliFile = (SmaliFile)configureByText(SmaliFileType.INSTANCE, completionTestClass); 110 111 PsiElement context = smaliFile.getPsiClass().getMethods()[0].getInstructions().get(0); 112 assertCompletionContains("v", context, new String[] {"v2", "v3"}, new String[] {"v0", "v1", "p0", "p1"}); 113 assertCompletionContains("p", context, new String[] {"p0", "p1"}, new String[] {"v0", "v1", "v2", "v3"}); 114 115 context = smaliFile.getPsiClass().getMethods()[0].getInstructions().get(2); 116 assertCompletionContains("v", context, new String[] {"v1", "v2", "v3"}, new String[] {"v0", "p0", "p1"}); 117 assertCompletionContains("p", context, new String[] {"p0", "p1"}, new String[] {"v0", "v1", "v2", "v3"}); 118 119 context = smaliFile.getPsiClass().getMethods()[0].getInstructions().get(6); 120 assertCompletionContains("v", context, new String[] {"v0", "v1", "v2", "v3"}, new String[] {"p0", "p1"}); 121 assertCompletionContains("p", context, new String[] {"p0", "p1"}, new String[] {}); 122 } 123 124 private static final String registerTypeTestText = "" + 125 ".class public LRegisterTypeTest;\n" + 126 ".super Ljava/lang/Object;\n" + 127 "\n" + 128 "# virtual methods\n" + 129 ".method public blah()V\n" + 130 " .registers 6\n" + 131 "\n" + 132 " .prologue\n" + 133 " const/16 v3, 0xa\n" + 134 "\n" + 135 " .line 7\n" + 136 " new-instance v0, Ljava/util/Random;\n" + 137 "\n" + 138 " invoke-direct {v0}, Ljava/util/Random;-><init>()V\n" + 139 "\n" + 140 " .line 9\n" + 141 " invoke-virtual {v0, v3}, Ljava/util/Random;->nextInt(I)I\n" + 142 "\n" + 143 " move-result v1\n" + 144 "\n" + 145 " const/4 v2, 0x5\n" + 146 "\n" + 147 " if-le v1, v2, :cond_26\n" + 148 "\n" + 149 " .line 10\n" + 150 " new-instance v1, Ljava/security/SecureRandom;\n" + 151 "\n" + 152 " invoke-direct {v1}, Ljava/security/SecureRandom;-><init>()V\n" + 153 "\n" + 154 " .line 14\n" + 155 " :goto_13\n" + 156 " sget-o<ref>bject v2, Ljava/lang/System;->out:Ljava/io/PrintStream;\n" + 157 "\n" + 158 " invoke-virtual {v1, v3}, Ljava/util/Random;->nextInt(I)I\n" + 159 "\n" + 160 " move-result v1\n" + 161 "\n" + 162 " invoke-virtual {v2, v1}, Ljava/io/PrintStream;->println(I)V\n" + 163 "\n" + 164 " .line 15\n" + 165 " sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;\n" + 166 "\n" + 167 " invoke-virtual {v0}, Ljava/lang/Object;->toString()Ljava/lang/String;\n" + 168 "\n" + 169 " move-result-object v0\n" + 170 "\n" + 171 " invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V\n" + 172 "\n" + 173 " .line 16\n" + 174 " return-void\n" + 175 "\n" + 176 " .line 12\n" + 177 " :cond_26\n" + 178 " invoke-virtual {p0}, LRegisterTypeTest;->getSerializable()Ljava/io/Serializable;\n" + 179 "\n" + 180 " move-result-object v1\n" + 181 "\n" + 182 " move-object v4, v1\n" + 183 "\n" + 184 " move-object v1, v0\n" + 185 "\n" + 186 " move-object v0, v4\n" + 187 "\n" + 188 " goto :goto_13\n" + 189 ".end method\n" + 190 "\n" + 191 ".method public getSerializable()Ljava/io/Serializable;\n" + 192 " .registers 2\n" + 193 "\n" + 194 " .prologue\n" + 195 " .line 19\n" + 196 " new-instance v0, Ljava/util/Random;\n" + 197 "\n" + 198 " invoke-direct {v0}, Ljava/util/Random;-><init>()V\n" + 199 "\n" + 200 " return-object v0\n" + 201 ".end method\n"; 202 203 public void testRegisterType() throws NoDataException { 204 SmaliFile smaliFile = (SmaliFile)configureByText(SmaliFileType.INSTANCE, 205 registerTypeTestText.replace("<ref>", "")); 206 207 int refOffset = registerTypeTestText.indexOf("<ref>"); 208 209 PsiElement context = smaliFile.findElementAt(refOffset); 210 assertVariableType(context.getParent(), "v1", "java.util.Random"); 211 assertVariableType(context.getParent(), "v0", "java.io.Serializable"); 212 } 213 214 public void testUnknownClass() { 215 String modifiedText = registerTypeTestText.replace("Random", "Rnd"); 216 SmaliFile smaliFile = (SmaliFile)configureByText(SmaliFileType.INSTANCE, 217 modifiedText.replace("<ref>", "")); 218 219 int refOffset = modifiedText.indexOf("<ref>"); 220 221 PsiElement context = smaliFile.findElementAt(refOffset); 222 assertVariableType(context.getParent(), "v1", "java.lang.Object"); 223 assertVariableType(context.getParent(), "v0", "java.lang.Object"); 224 } 225 226 private void assertCompletionContains(String completionText, PsiElement context, String[] expectedItems, 227 String[] disallowedItems) { 228 SmaliCodeFragmentFactory codeFragmentFactory = new SmaliCodeFragmentFactory(); 229 JavaCodeFragment fragment = codeFragmentFactory.createCodeFragment( 230 new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, completionText), 231 context, getProject()); 232 233 Editor editor = createEditor(fragment.getVirtualFile()); 234 editor.getCaretModel().moveToOffset(completionText.length()); 235 236 new CodeCompletionHandlerBase(CompletionType.BASIC).invokeCompletion(getProject(), editor); 237 List<LookupElement> elements = LookupManager.getInstance(getProject()).getActiveLookup().getItems(); 238 239 HashSet expectedSet = Sets.newHashSet(expectedItems); 240 HashSet disallowedSet = Sets.newHashSet(disallowedItems); 241 242 for (LookupElement element: elements) { 243 expectedSet.remove(element.toString()); 244 Assert.assertFalse(disallowedSet.contains(element.toString())); 245 } 246 247 Assert.assertTrue(expectedSet.size() == 0); 248 } 249 250 private void assertVariableType(PsiElement context, String variableName, String expectedType) { 251 SmaliCodeFragmentFactory codeFragmentFactory = new SmaliCodeFragmentFactory(); 252 JavaCodeFragment fragment = codeFragmentFactory.createCodeFragment( 253 new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, variableName), 254 context, getProject()); 255 256 Editor editor = createEditor(fragment.getVirtualFile()); 257 editor.getCaretModel().moveToOffset(1); 258 259 PsiElement element = fragment.findElementAt(0); 260 Assert.assertTrue(element.getParent() instanceof PsiReferenceExpressionImpl); 261 PsiReferenceExpressionImpl reference = (PsiReferenceExpressionImpl)element.getParent(); 262 Assert.assertEquals(expectedType, reference.getType().getCanonicalText()); 263 } 264 265 protected Editor createEditor(@NotNull VirtualFile file) { 266 PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); 267 Editor editor = FileEditorManager.getInstance(getProject()).openTextEditor( 268 new OpenFileDescriptor(getProject(), file, 0), false); 269 DaemonCodeAnalyzer.getInstance(getProject()).restart(); 270 271 ((EditorImpl)editor).setCaretActive(); 272 return editor; 273 } 274 275 @Override 276 protected Sdk getTestProjectJdk() { 277 return JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk(); 278 } 279 } 280