blob: c61d3e448a3b637443c130944bab7c6edacc33f5 [file] [log] [blame]
/*
* 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();
}
}