Home | History | Annotate | Download | only in smalidea
      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