From 37d1612e2f272b12d7ee556e05bb92f0e1a940b8 Mon Sep 17 00:00:00 2001 From: Ben Gruver Date: Thu, 12 Mar 2015 20:16:25 -0700 Subject: [PATCH] Add find usages support for methods --- smalidea/META-INF/plugin.xml | 1 + .../findUsages/SmaliUsageTargetProvider.java | 76 +++++++++++++++ .../smalidea/findUsages/SmaliWordScanner.java | 52 ++++++++++ .../org/jf/smalidea/psi/impl/SmaliMethod.java | 8 ++ .../findUsages/FindMethodUsagesTest.java | 95 +++++++++++++++++++ .../smalidea/findUsages/FindUsagesTest.java | 48 +++++++--- 6 files changed, 268 insertions(+), 12 deletions(-) create mode 100644 smalidea/src/main/java/org/jf/smalidea/findUsages/SmaliUsageTargetProvider.java create mode 100644 smalidea/src/test/java/org/jf/smalidea/findUsages/FindMethodUsagesTest.java diff --git a/smalidea/META-INF/plugin.xml b/smalidea/META-INF/plugin.xml index a13d0d19..4f827bdf 100644 --- a/smalidea/META-INF/plugin.xml +++ b/smalidea/META-INF/plugin.xml @@ -29,6 +29,7 @@ + diff --git a/smalidea/src/main/java/org/jf/smalidea/findUsages/SmaliUsageTargetProvider.java b/smalidea/src/main/java/org/jf/smalidea/findUsages/SmaliUsageTargetProvider.java new file mode 100644 index 00000000..e87782b6 --- /dev/null +++ b/smalidea/src/main/java/org/jf/smalidea/findUsages/SmaliUsageTargetProvider.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015, 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.findUsages; + +import com.intellij.codeInsight.TargetElementUtilBase; +import com.intellij.find.findUsages.PsiElement2UsageTargetAdapter; +import com.intellij.lang.ASTNode; +import com.intellij.openapi.editor.Editor; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.usages.UsageTarget; +import com.intellij.usages.UsageTargetProvider; +import org.jetbrains.annotations.Nullable; +import org.jf.smalidea.SmaliTokens; +import org.jf.smalidea.psi.impl.SmaliMemberName; + +/** + * A usage target provider for smali member names consisting of primitive types. + * + * For member names like IIII, the default logic to find the usage target doesn't work, due to the member + * name being split up into multiple leaf tokens. + */ +public class SmaliUsageTargetProvider implements UsageTargetProvider { + @Nullable @Override public UsageTarget[] getTargets(Editor editor, PsiFile file) { + PsiElement element = file.findElementAt( + TargetElementUtilBase.adjustOffset(file, editor.getDocument(), editor.getCaretModel().getOffset())); + if (element == null) { + return null; + } + return getTargets(element); + } + + @Nullable @Override public UsageTarget[] getTargets(PsiElement element) { + ASTNode node = element.getNode(); + if (node == null) { + return null; + } + + if (node.getElementType() == SmaliTokens.PARAM_LIST_OR_ID_PRIMITIVE_TYPE) { + PsiElement parent = element.getParent(); + if (parent instanceof SmaliMemberName) { + return new UsageTarget[] { new PsiElement2UsageTargetAdapter(parent.getParent()) }; + } + } + return null; + } +} diff --git a/smalidea/src/main/java/org/jf/smalidea/findUsages/SmaliWordScanner.java b/smalidea/src/main/java/org/jf/smalidea/findUsages/SmaliWordScanner.java index 0252f796..56bbdafb 100644 --- a/smalidea/src/main/java/org/jf/smalidea/findUsages/SmaliWordScanner.java +++ b/smalidea/src/main/java/org/jf/smalidea/findUsages/SmaliWordScanner.java @@ -35,6 +35,7 @@ import com.intellij.lang.cacheBuilder.WordOccurrence; import com.intellij.lang.cacheBuilder.WordOccurrence.Kind; import com.intellij.lang.cacheBuilder.WordsScanner; import com.intellij.psi.tree.IElementType; +import com.intellij.psi.tree.TokenSet; import com.intellij.util.Processor; import org.jetbrains.annotations.NotNull; import org.jf.smalidea.SmaliLexer; @@ -42,6 +43,47 @@ import org.jf.smalidea.SmaliTokens; public class SmaliWordScanner implements WordsScanner { + private static final TokenSet MEMBER_NAME_TOKENS = TokenSet.create( + SmaliTokens.MEMBER_NAME, + SmaliTokens.SIMPLE_NAME, + SmaliTokens.ACCESS_SPEC, + SmaliTokens.VERIFICATION_ERROR_TYPE, + SmaliTokens.POSITIVE_INTEGER_LITERAL, + SmaliTokens.NEGATIVE_INTEGER_LITERAL, + SmaliTokens.FLOAT_LITERAL_OR_ID, + SmaliTokens.DOUBLE_LITERAL_OR_ID, + SmaliTokens.BOOL_LITERAL, + SmaliTokens.NULL_LITERAL, + SmaliTokens.REGISTER, + SmaliTokens.PRIMITIVE_TYPE, + SmaliTokens.VOID_TYPE, + SmaliTokens.ANNOTATION_VISIBILITY, + SmaliTokens.INSTRUCTION_FORMAT10t, + SmaliTokens.INSTRUCTION_FORMAT10x, + SmaliTokens.INSTRUCTION_FORMAT10x_ODEX, + SmaliTokens.INSTRUCTION_FORMAT11x, + SmaliTokens.INSTRUCTION_FORMAT12x_OR_ID, + SmaliTokens.INSTRUCTION_FORMAT21c_FIELD, + SmaliTokens.INSTRUCTION_FORMAT21c_FIELD_ODEX, + SmaliTokens.INSTRUCTION_FORMAT21c_STRING, + SmaliTokens.INSTRUCTION_FORMAT21c_TYPE, + SmaliTokens.INSTRUCTION_FORMAT21t, + SmaliTokens.INSTRUCTION_FORMAT22c_FIELD, + SmaliTokens.INSTRUCTION_FORMAT22c_FIELD_ODEX, + SmaliTokens.INSTRUCTION_FORMAT22c_TYPE, + SmaliTokens.INSTRUCTION_FORMAT22cs_FIELD, + SmaliTokens.INSTRUCTION_FORMAT22s_OR_ID, + SmaliTokens.INSTRUCTION_FORMAT22t, + SmaliTokens.INSTRUCTION_FORMAT23x, + SmaliTokens.INSTRUCTION_FORMAT31i_OR_ID, + SmaliTokens.INSTRUCTION_FORMAT31t, + SmaliTokens.INSTRUCTION_FORMAT35c_METHOD, + SmaliTokens.INSTRUCTION_FORMAT35c_METHOD_ODEX, + SmaliTokens.INSTRUCTION_FORMAT35c_TYPE, + SmaliTokens.INSTRUCTION_FORMAT35mi_METHOD, + SmaliTokens.INSTRUCTION_FORMAT35ms_METHOD, + SmaliTokens.INSTRUCTION_FORMAT51l); + @Override public void processWords(CharSequence fileText, Processor processor) { SmaliLexer lexer = new SmaliLexer(); lexer.start(fileText); @@ -50,6 +92,16 @@ public class SmaliWordScanner implements WordsScanner { while (type != null) { if (type == SmaliTokens.CLASS_DESCRIPTOR) { processClassDescriptor(fileText, lexer.getTokenStart(), lexer.getTokenEnd(), processor); + } else if (MEMBER_NAME_TOKENS.contains(type)) { + processor.process(new WordOccurrence(fileText, lexer.getTokenStart(), lexer.getTokenEnd(), Kind.CODE)); + } else if (type == SmaliTokens.PARAM_LIST_OR_ID_PRIMITIVE_TYPE) { + int tokenStart = lexer.getTokenStart(); + while (type == SmaliTokens.PARAM_LIST_OR_ID_PRIMITIVE_TYPE) { + lexer.advance(); + type = lexer.getTokenType(); + } + int tokenEnd = lexer.getTokenStart(); + processor.process(new WordOccurrence(fileText, tokenStart, tokenEnd, Kind.CODE)); } lexer.advance(); type = lexer.getTokenType(); diff --git a/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliMethod.java b/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliMethod.java index ffde5bcf..400e509c 100644 --- a/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliMethod.java +++ b/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliMethod.java @@ -322,4 +322,12 @@ public class SmaliMethod extends SmaliStubBasedPsiElement super.subtreeChanged(); methodAnalyzer = null; } + + @Override public int getTextOffset() { + SmaliMemberName smaliMemberName = getNameIdentifier(); + if (smaliMemberName != null) { + return smaliMemberName.getTextOffset(); + } + return super.getTextOffset(); + } } diff --git a/smalidea/src/test/java/org/jf/smalidea/findUsages/FindMethodUsagesTest.java b/smalidea/src/test/java/org/jf/smalidea/findUsages/FindMethodUsagesTest.java new file mode 100644 index 00000000..311c9eef --- /dev/null +++ b/smalidea/src/test/java/org/jf/smalidea/findUsages/FindMethodUsagesTest.java @@ -0,0 +1,95 @@ +/* + * Copyright 2015, 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.findUsages; + +public class FindMethodUsagesTest extends FindUsagesTest { + public void testSmaliUsageInSmaliFile() throws Exception { + addFile("blah.smali", "" + + ".class public Lblah;\n" + + ".super Ljava/lang/Object;\n" + + ".method abstract blahMethod()V\n" + + ".end method"); + addFile("blarg.smali", "" + + ".class public Lblarg;\n" + + ".super Ljava/lang/Object;\n" + + ".method abstract blargMethod()V\n" + + " invoke-virtual {v0}, Lblah;->blahMethod()V\n" + + ".end method"); + doTest(); + } + + public void testSmaliUsageInJavaFile() throws Exception { + addFile("blah.smali", "" + + ".class public Lblah;\n" + + ".super Ljava/lang/Object;\n" + + ".method abstract blahMethod()V\n" + + ".end method"); + addFile("blarg.java", "" + + "public class blarg {\n" + + " public void blargMethod() {\n" + + " blah b = new blah();\n" + + " b.blahMethod();\n" + + " }\n" + + "}"); + doTest(); + } + + public void testJavaUsageInSmaliFile() throws Exception { + addFile("blah.java", "" + + "public class blah {\n" + + " public void blahMethod() {\n" + + " }\n" + + "}"); + addFile("blarg.smali", "" + + ".class public Lblarg;\n" + + ".super Ljava/lang/Object;\n" + + ".method abstract blargMethod()V\n" + + " invoke-virtual {v0}, Lblah;->blahMethod()V\n" + + ".end method"); + doTest(); + } + + public void testPrimitiveListMethod() throws Exception { + addFile("blah.smali", "" + + ".class public Lblah;\n" + + ".super Ljava/lang/Object;\n" + + ".method abstract IIII()V\n" + + ".end method"); + addFile("blarg.smali", "" + + ".class public Lblarg;\n" + + ".super Ljava/lang/Object;\n" + + ".method abstract blargMethod()V\n" + + " invoke-virtual {v0}, Lblah;->IIII()V\n" + + ".end method"); + doTest(); + } +} diff --git a/smalidea/src/test/java/org/jf/smalidea/findUsages/FindUsagesTest.java b/smalidea/src/test/java/org/jf/smalidea/findUsages/FindUsagesTest.java index 84000505..f4e2b0ff 100644 --- a/smalidea/src/test/java/org/jf/smalidea/findUsages/FindUsagesTest.java +++ b/smalidea/src/test/java/org/jf/smalidea/findUsages/FindUsagesTest.java @@ -32,6 +32,7 @@ package org.jf.smalidea.findUsages; import com.google.common.collect.Lists; +import com.intellij.codeInsight.TargetElementUtilBase; import com.intellij.find.FindManager; import com.intellij.find.findUsages.FindUsagesHandler; import com.intellij.find.findUsages.FindUsagesManager; @@ -42,6 +43,9 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.PsiReference; import com.intellij.testFramework.PsiTestCase; import com.intellij.usageView.UsageInfo; +import com.intellij.usages.PsiElementUsageTarget; +import com.intellij.usages.UsageTarget; +import com.intellij.usages.UsageTargetUtil; import com.intellij.util.CommonProcessors; import org.jetbrains.annotations.NotNull; import org.junit.Assert; @@ -101,18 +105,41 @@ public abstract class FindUsagesTest extends PsiTestCase { } protected void doTest() { + PsiReference reference = null; + PsiElement targetElement = null; + for (TestFile testFile: testFiles) { int refIndex = testFile.getRefIndex(); if (refIndex != -1) { - reference = testFile.psiFile.findReferenceAt(refIndex); + PsiElement element = testFile.psiFile.findElementAt(refIndex); + + UsageTarget[] targets = UsageTargetUtil.findUsageTargets(element); + if (targets != null) { + for (UsageTarget target : targets) { + if (target instanceof PsiElementUsageTarget) { + targetElement = ((PsiElementUsageTarget)target).getElement(); + break; + } + } + } + + if (targetElement == null) { + reference = testFile.psiFile.findReferenceAt(refIndex); + if (reference != null) { + targetElement = reference.resolve(); + } else { + targetElement = TargetElementUtilBase.getInstance().getNamedElement( + testFile.psiFile.findElementAt(refIndex), 0); + } + } break; } } - Assert.assertNotNull(reference); + Assert.assertNotNull(targetElement); - Collection usages = findUsages(reference); + Collection usages = findUsages(targetElement); for (TestFile testFile: testFiles) { assertUsages(testFile, usages); } @@ -143,27 +170,24 @@ public abstract class FindUsagesTest extends PsiTestCase { Assert.assertEquals(0, fileUsages.size()); } - private Collection findUsages(@NotNull PsiReference reference) { - PsiElement resolved = reference.resolve(); - Assert.assertNotNull(resolved); - + private Collection findUsages(@NotNull PsiElement element) { FindUsagesManager findUsagesManager = ((FindManagerImpl)FindManager.getInstance(getProject())).getFindUsagesManager(); FindUsagesHandler findUsagesHandler = - findUsagesManager.getFindUsagesHandler(resolved, false); + findUsagesManager.getFindUsagesHandler(element, false); Assert.assertNotNull(findUsagesHandler); final FindUsagesOptions options = findUsagesHandler.getFindUsagesOptions(); final CommonProcessors.CollectProcessor processor = new CommonProcessors.CollectProcessor(); - for (PsiElement element : findUsagesHandler.getPrimaryElements()) { - findUsagesHandler.processElementUsages(element, processor, options); + for (PsiElement primaryElement : findUsagesHandler.getPrimaryElements()) { + findUsagesHandler.processElementUsages(primaryElement, processor, options); } - for (PsiElement element : findUsagesHandler.getSecondaryElements()) { - findUsagesHandler.processElementUsages(element, processor, options); + for (PsiElement secondaryElement: findUsagesHandler.getSecondaryElements()) { + findUsagesHandler.processElementUsages(secondaryElement, processor, options); } return processor.getResults();