diff options
author | Ben Gruver <bgruv@google.com> | 2016-02-14 16:29:21 -0800 |
---|---|---|
committer | Ben Gruver <bgruv@google.com> | 2016-02-20 12:00:05 -0800 |
commit | f16ea398a1df52e3bb6afbbb274c66e23412ae58 (patch) | |
tree | 6f02f70e563cecc99dcde2c0a89c04d07127b125 | |
parent | 92f8ec50c5d2675084ad1522a7ff429fe9f3b6ab (diff) | |
download | smali-f16ea398a1df52e3bb6afbbb274c66e23412ae58.tar.gz |
Don't require an active debugging session when making a code fragment
This is needed, e.g. when editing a conditional statement in the
breakpoint window.
3 files changed, 237 insertions, 3 deletions
diff --git a/smalidea/src/main/java/org/jf/smalidea/debugging/SmaliCodeFragmentFactory.java b/smalidea/src/main/java/org/jf/smalidea/debugging/SmaliCodeFragmentFactory.java index 1968c1b9..3a84af88 100644 --- a/smalidea/src/main/java/org/jf/smalidea/debugging/SmaliCodeFragmentFactory.java +++ b/smalidea/src/main/java/org/jf/smalidea/debugging/SmaliCodeFragmentFactory.java @@ -33,7 +33,6 @@ package org.jf.smalidea.debugging; import com.google.common.collect.Maps; import com.intellij.debugger.DebuggerManagerEx; -import com.intellij.debugger.SourcePosition; import com.intellij.debugger.engine.DebugProcessImpl; import com.intellij.debugger.engine.evaluation.*; import com.intellij.debugger.engine.events.DebuggerCommandImpl; @@ -47,6 +46,7 @@ import com.intellij.psi.JavaCodeFragment; import com.intellij.psi.JavaRecursiveElementVisitor; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiLocalVariable; +import com.intellij.psi.util.PsiMatchers; import com.sun.jdi.*; import com.sun.tools.jdi.LocalVariableImpl; import com.sun.tools.jdi.LocationImpl; @@ -56,6 +56,7 @@ import org.jf.smalidea.SmaliFileType; import org.jf.smalidea.SmaliLanguage; import org.jf.smalidea.psi.impl.SmaliInstruction; import org.jf.smalidea.psi.impl.SmaliMethod; +import org.jf.smalidea.util.PsiUtil; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -89,8 +90,18 @@ public class SmaliCodeFragmentFactory extends DefaultCodeFragmentFactory { final DebuggerContextImpl debuggerContext = DebuggerManagerEx.getInstanceEx(project).getContext(); - SourcePosition position = debuggerContext.getSourcePosition(); - SmaliInstruction currentInstruction = (SmaliInstruction)position.getElementAt(); + SmaliInstruction currentInstruction = (SmaliInstruction)PsiUtil.searchBackward(originalContext, + PsiMatchers.hasClass(SmaliInstruction.class), + PsiMatchers.hasClass(SmaliMethod.class)); + + if (currentInstruction == null) { + currentInstruction = (SmaliInstruction)PsiUtil.searchForward(originalContext, + PsiMatchers.hasClass(SmaliInstruction.class), + PsiMatchers.hasClass(SmaliMethod.class)); + if (currentInstruction == null) { + return originalContext; + } + } final SmaliMethod containingMethod = currentInstruction.getParentMethod(); AnalyzedInstruction analyzedInstruction = currentInstruction.getAnalyzedInstruction(); diff --git a/smalidea/src/main/java/org/jf/smalidea/util/PsiUtil.java b/smalidea/src/main/java/org/jf/smalidea/util/PsiUtil.java new file mode 100644 index 00000000..72c5a133 --- /dev/null +++ b/smalidea/src/main/java/org/jf/smalidea/util/PsiUtil.java @@ -0,0 +1,73 @@ +/* + * 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.util; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiMatcherExpression; + +public class PsiUtil { + public static PsiElement searchBackward(PsiElement element, PsiMatcherExpression matcher, + PsiMatcherExpression until) { + while (!matcher.match(element)) { + if (until.match(element)) { + return null; + } + PsiElement prev = element.getPrevSibling(); + if (prev == null) { + prev = element.getParent(); + if (prev == null) { + return null; + } + } + element = prev; + } + return element; + } + + public static PsiElement searchForward(PsiElement element, PsiMatcherExpression matcher, + PsiMatcherExpression until) { + while (!matcher.match(element)) { + if (until.match(element)) { + return null; + } + PsiElement next = element.getNextSibling(); + if (next == null) { + next = element.getParent(); + if (next == null) { + return null; + } + } + element = next; + } + return element; + } +} diff --git a/smalidea/src/test/java/org/jf/smalidea/SmaliCodeFragmentFactoryTest.java b/smalidea/src/test/java/org/jf/smalidea/SmaliCodeFragmentFactoryTest.java new file mode 100644 index 00000000..e987a397 --- /dev/null +++ b/smalidea/src/test/java/org/jf/smalidea/SmaliCodeFragmentFactoryTest.java @@ -0,0 +1,150 @@ +/* + * 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.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.vfs.VirtualFile; +import com.intellij.psi.JavaCodeFragment; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; +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 LightCodeInsightFixtureTestCase { + private static final String testClass = + ".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" + + "\n" + + " .line 179\n" + + " if-nez p1, :cond_5\n" + + "\n" + + " move v0, v1\n" + + "\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" + + "\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)myFixture.addFileToProject("my/pkg/blah.smali", testClass); + + 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"}); + } + + 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); + } + + 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; + } +} |