| /* |
| * Copyright 2016, Google Inc. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| package org.jf.smalidea; |
| |
| import com.google.common.collect.Sets; |
| import com.intellij.codeInsight.CodeInsightTestCase; |
| import com.intellij.codeInsight.completion.CodeCompletionHandlerBase; |
| import com.intellij.codeInsight.completion.CompletionType; |
| import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; |
| import com.intellij.codeInsight.lookup.LookupElement; |
| import com.intellij.codeInsight.lookup.LookupManager; |
| import com.intellij.debugger.NoDataException; |
| import com.intellij.debugger.engine.evaluation.CodeFragmentKind; |
| import com.intellij.debugger.engine.evaluation.TextWithImportsImpl; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.editor.impl.EditorImpl; |
| import com.intellij.openapi.fileEditor.FileEditorManager; |
| import com.intellij.openapi.fileEditor.OpenFileDescriptor; |
| import com.intellij.openapi.projectRoots.Sdk; |
| import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.JavaCodeFragment; |
| import com.intellij.psi.PsiDocumentManager; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.impl.source.tree.java.PsiReferenceExpressionImpl; |
| import org.jetbrains.annotations.NotNull; |
| import org.jf.smalidea.debugging.SmaliCodeFragmentFactory; |
| import org.jf.smalidea.psi.impl.SmaliFile; |
| import org.junit.Assert; |
| |
| import java.util.HashSet; |
| import java.util.List; |
| |
| public class SmaliCodeFragmentFactoryTest extends CodeInsightTestCase { |
| private static final String completionTestClass = |
| ".class public Lmy/pkg/blah; .super Ljava/lang/Object;\n" + |
| ".method public getRandomParentType(I)I\n" + |
| " .registers 4\n" + |
| " .param p1, \"edge\" # I\n" + |
| "\n" + |
| " .prologue\n" + |
| " const/4 v1, 0x2\n" + // 0 |
| "\n" + |
| " .line 179\n" + |
| " if-nez p1, :cond_5\n" + |
| "\n" + |
| " move v0, v1\n" + // 2 |
| "\n" + |
| " .line 185\n" + |
| " :goto_4\n" + |
| " return v0\n" + |
| "\n" + |
| " .line 182\n" + |
| " :cond_5\n" + |
| " if-ne p1, v1, :cond_f\n" + |
| "\n" + |
| " .line 183\n" + |
| " sget-object v0, Lorg/jf/Penroser/PenroserApp;->random:Ljava/util/Random;\n" + |
| "\n" + |
| " const/4 v1, 0x3\n" + // 6 |
| "\n" + |
| " invoke-virtual {v0, v1}, Ljava/util/Random;->nextInt(I)I\n" + |
| "\n" + |
| " move-result v0\n" + |
| "\n" + |
| " goto :goto_4\n" + |
| "\n" + |
| " .line 185\n" + |
| " :cond_f\n" + |
| " sget-object v0, Lorg/jf/Penroser/PenroserApp;->random:Ljava/util/Random;\n" + |
| "\n" + |
| " invoke-virtual {v0, v1}, Ljava/util/Random;->nextInt(I)I\n" + |
| "\n" + |
| " move-result v0\n" + |
| "\n" + |
| " goto :goto_4\n" + |
| ".end method"; |
| |
| public void testCompletion() throws NoDataException { |
| SmaliFile smaliFile = (SmaliFile)configureByText(SmaliFileType.INSTANCE, completionTestClass); |
| |
| PsiElement context = smaliFile.getPsiClass().getMethods()[0].getInstructions().get(0); |
| assertCompletionContains("v", context, new String[] {"v2", "v3"}, new String[] {"v0", "v1", "p0", "p1"}); |
| assertCompletionContains("p", context, new String[] {"p0", "p1"}, new String[] {"v0", "v1", "v2", "v3"}); |
| |
| context = smaliFile.getPsiClass().getMethods()[0].getInstructions().get(2); |
| assertCompletionContains("v", context, new String[] {"v1", "v2", "v3"}, new String[] {"v0", "p0", "p1"}); |
| assertCompletionContains("p", context, new String[] {"p0", "p1"}, new String[] {"v0", "v1", "v2", "v3"}); |
| |
| context = smaliFile.getPsiClass().getMethods()[0].getInstructions().get(6); |
| assertCompletionContains("v", context, new String[] {"v0", "v1", "v2", "v3"}, new String[] {"p0", "p1"}); |
| assertCompletionContains("p", context, new String[] {"p0", "p1"}, new String[] {}); |
| } |
| |
| private static final String registerTypeTestText = "" + |
| ".class public LRegisterTypeTest;\n" + |
| ".super Ljava/lang/Object;\n" + |
| "\n" + |
| "# virtual methods\n" + |
| ".method public blah()V\n" + |
| " .registers 6\n" + |
| "\n" + |
| " .prologue\n" + |
| " const/16 v3, 0xa\n" + |
| "\n" + |
| " .line 7\n" + |
| " new-instance v0, Ljava/util/Random;\n" + |
| "\n" + |
| " invoke-direct {v0}, Ljava/util/Random;-><init>()V\n" + |
| "\n" + |
| " .line 9\n" + |
| " invoke-virtual {v0, v3}, Ljava/util/Random;->nextInt(I)I\n" + |
| "\n" + |
| " move-result v1\n" + |
| "\n" + |
| " const/4 v2, 0x5\n" + |
| "\n" + |
| " if-le v1, v2, :cond_26\n" + |
| "\n" + |
| " .line 10\n" + |
| " new-instance v1, Ljava/security/SecureRandom;\n" + |
| "\n" + |
| " invoke-direct {v1}, Ljava/security/SecureRandom;-><init>()V\n" + |
| "\n" + |
| " .line 14\n" + |
| " :goto_13\n" + |
| " sget-o<ref>bject v2, Ljava/lang/System;->out:Ljava/io/PrintStream;\n" + |
| "\n" + |
| " invoke-virtual {v1, v3}, Ljava/util/Random;->nextInt(I)I\n" + |
| "\n" + |
| " move-result v1\n" + |
| "\n" + |
| " invoke-virtual {v2, v1}, Ljava/io/PrintStream;->println(I)V\n" + |
| "\n" + |
| " .line 15\n" + |
| " sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;\n" + |
| "\n" + |
| " invoke-virtual {v0}, Ljava/lang/Object;->toString()Ljava/lang/String;\n" + |
| "\n" + |
| " move-result-object v0\n" + |
| "\n" + |
| " invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V\n" + |
| "\n" + |
| " .line 16\n" + |
| " return-void\n" + |
| "\n" + |
| " .line 12\n" + |
| " :cond_26\n" + |
| " invoke-virtual {p0}, LRegisterTypeTest;->getSerializable()Ljava/io/Serializable;\n" + |
| "\n" + |
| " move-result-object v1\n" + |
| "\n" + |
| " move-object v4, v1\n" + |
| "\n" + |
| " move-object v1, v0\n" + |
| "\n" + |
| " move-object v0, v4\n" + |
| "\n" + |
| " goto :goto_13\n" + |
| ".end method\n" + |
| "\n" + |
| ".method public getSerializable()Ljava/io/Serializable;\n" + |
| " .registers 2\n" + |
| "\n" + |
| " .prologue\n" + |
| " .line 19\n" + |
| " new-instance v0, Ljava/util/Random;\n" + |
| "\n" + |
| " invoke-direct {v0}, Ljava/util/Random;-><init>()V\n" + |
| "\n" + |
| " return-object v0\n" + |
| ".end method\n"; |
| |
| public void testRegisterType() throws NoDataException { |
| SmaliFile smaliFile = (SmaliFile)configureByText(SmaliFileType.INSTANCE, |
| registerTypeTestText.replace("<ref>", "")); |
| |
| int refOffset = registerTypeTestText.indexOf("<ref>"); |
| |
| PsiElement context = smaliFile.findElementAt(refOffset); |
| assertVariableType(context.getParent(), "v1", "java.util.Random"); |
| assertVariableType(context.getParent(), "v0", "java.io.Serializable"); |
| } |
| |
| public void testUnknownClass() { |
| String modifiedText = registerTypeTestText.replace("Random", "Rnd"); |
| SmaliFile smaliFile = (SmaliFile)configureByText(SmaliFileType.INSTANCE, |
| modifiedText.replace("<ref>", "")); |
| |
| int refOffset = modifiedText.indexOf("<ref>"); |
| |
| PsiElement context = smaliFile.findElementAt(refOffset); |
| assertVariableType(context.getParent(), "v1", "java.lang.Object"); |
| assertVariableType(context.getParent(), "v0", "java.lang.Object"); |
| } |
| |
| private void assertCompletionContains(String completionText, PsiElement context, String[] expectedItems, |
| String[] disallowedItems) { |
| SmaliCodeFragmentFactory codeFragmentFactory = new SmaliCodeFragmentFactory(); |
| JavaCodeFragment fragment = codeFragmentFactory.createCodeFragment( |
| new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, completionText), |
| context, getProject()); |
| |
| Editor editor = createEditor(fragment.getVirtualFile()); |
| editor.getCaretModel().moveToOffset(completionText.length()); |
| |
| new CodeCompletionHandlerBase(CompletionType.BASIC).invokeCompletion(getProject(), editor); |
| List<LookupElement> elements = LookupManager.getInstance(getProject()).getActiveLookup().getItems(); |
| |
| HashSet expectedSet = Sets.newHashSet(expectedItems); |
| HashSet disallowedSet = Sets.newHashSet(disallowedItems); |
| |
| for (LookupElement element: elements) { |
| expectedSet.remove(element.toString()); |
| Assert.assertFalse(disallowedSet.contains(element.toString())); |
| } |
| |
| Assert.assertTrue(expectedSet.size() == 0); |
| } |
| |
| private void assertVariableType(PsiElement context, String variableName, String expectedType) { |
| SmaliCodeFragmentFactory codeFragmentFactory = new SmaliCodeFragmentFactory(); |
| JavaCodeFragment fragment = codeFragmentFactory.createCodeFragment( |
| new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, variableName), |
| context, getProject()); |
| |
| Editor editor = createEditor(fragment.getVirtualFile()); |
| editor.getCaretModel().moveToOffset(1); |
| |
| PsiElement element = fragment.findElementAt(0); |
| Assert.assertTrue(element.getParent() instanceof PsiReferenceExpressionImpl); |
| PsiReferenceExpressionImpl reference = (PsiReferenceExpressionImpl)element.getParent(); |
| Assert.assertEquals(expectedType, reference.getType().getCanonicalText()); |
| } |
| |
| protected Editor createEditor(@NotNull VirtualFile file) { |
| PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); |
| Editor editor = FileEditorManager.getInstance(getProject()).openTextEditor( |
| new OpenFileDescriptor(getProject(), file, 0), false); |
| DaemonCodeAnalyzer.getInstance(getProject()).restart(); |
| |
| ((EditorImpl)editor).setCaretActive(); |
| return editor; |
| } |
| |
| @Override |
| protected Sdk getTestProjectJdk() { |
| return JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk(); |
| } |
| } |