diff --git a/smalidea/build.gradle b/smalidea/build.gradle index 408b02c0..dcf6c5c1 100644 --- a/smalidea/build.gradle +++ b/smalidea/build.gradle @@ -126,7 +126,7 @@ repositories { } dependencies { - compile 'org.smali:smali:2.1.2' + compile 'org.smali:smali:2.1.2-205bf333' compile 'org.antlr:antlr-runtime:3.5.2' compile 'com.google.code.gson:gson:2.3.1' 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 469d27b9..abd5c90e 100644 --- a/smalidea/src/main/java/org/jf/smalidea/debugging/SmaliCodeFragmentFactory.java +++ b/smalidea/src/main/java/org/jf/smalidea/debugging/SmaliCodeFragmentFactory.java @@ -58,6 +58,7 @@ import org.jf.smalidea.SmaliLanguage; import org.jf.smalidea.debugging.value.LazyValue; import org.jf.smalidea.psi.impl.SmaliInstruction; import org.jf.smalidea.psi.impl.SmaliMethod; +import org.jf.smalidea.util.NameUtils; import org.jf.smalidea.util.PsiUtil; import java.lang.reflect.Constructor; @@ -225,10 +226,12 @@ public class SmaliCodeFragmentFactory extends DefaultCodeFragmentFactory { case RegisterType.UNINIT_REF: case RegisterType.UNINIT_THIS: case RegisterType.REFERENCE: - variablesText.append("Object v").append(i).append(";\n"); - registerMap.put("v" + i, "Ljava/lang/Object;"); + String smaliType = registerType.type.getType(); + String javaType = NameUtils.smaliToJavaType(smaliType); + variablesText.append(javaType).append(" v").append(i).append(";\n"); + registerMap.put("v" + i, smaliType); if (parameterRegisterNumber >= 0) { - variablesText.append("Object p").append(parameterRegisterNumber).append(";\n"); + variablesText.append(javaType).append(" p").append(parameterRegisterNumber).append(";\n"); registerMap.put("p" + parameterRegisterNumber, "Ljava/lang/Object;"); } break; diff --git a/smalidea/src/main/java/org/jf/smalidea/dexlib/SmalideaClassDef.java b/smalidea/src/main/java/org/jf/smalidea/dexlib/SmalideaClassDef.java new file mode 100644 index 00000000..11df39c3 --- /dev/null +++ b/smalidea/src/main/java/org/jf/smalidea/dexlib/SmalideaClassDef.java @@ -0,0 +1,227 @@ +/* + * 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.dexlib; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiField; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiModifierList; +import org.jf.dexlib2.AccessFlags; +import org.jf.dexlib2.base.reference.BaseTypeReference; +import org.jf.dexlib2.iface.Annotation; +import org.jf.dexlib2.iface.ClassDef; +import org.jf.dexlib2.iface.Field; +import org.jf.dexlib2.iface.Method; +import org.jf.smalidea.util.NameUtils; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +public class SmalideaClassDef extends BaseTypeReference implements ClassDef { + private final PsiClass psiClass; + + public SmalideaClassDef(PsiClass psiClass) { + this.psiClass = psiClass; + } + + @Override public int getAccessFlags() { + PsiModifierList modifierList = psiClass.getModifierList(); + int flags = 0; + + if (modifierList == null) { + return flags; + } + + if (modifierList.hasModifierProperty("public")) { + flags |= AccessFlags.PUBLIC.getValue(); + } + + if (modifierList.hasModifierProperty("final")) { + flags |= AccessFlags.FINAL.getValue(); + } + + if (modifierList.hasModifierProperty("abstract")) { + flags |= AccessFlags.ABSTRACT.getValue(); + } + + if (psiClass.isInterface()) { + flags |= AccessFlags.INTERFACE.getValue(); + } + + if (psiClass.isEnum()) { + flags |= AccessFlags.ENUM.getValue(); + } + + if (psiClass.isAnnotationType()) { + flags |= AccessFlags.ANNOTATION.getValue(); + } + + return flags; + } + + @Nonnull @Override public String getType() { + // TODO: properly handle inner classes.. + String javaName = psiClass.getQualifiedName(); + if (javaName == null) { + throw new RuntimeException("I don't know what to do here... Is this even possible?"); + } + return NameUtils.javaToSmaliType(javaName); + } + + @Nullable @Override public String getSuperclass() { + PsiClass superClass = psiClass.getSuperClass(); + if (superClass == null) { + return null; + } + String javaName = superClass.getQualifiedName(); + if (javaName == null) { + throw new RuntimeException("I don't know what to do here... Is this even possible?"); + } + return NameUtils.javaToSmaliType(javaName); + } + + @Nonnull @Override public List getInterfaces() { + List interfaceList = Lists.newArrayList(); + PsiClass[] interfaces = psiClass.getInterfaces(); + if (interfaces == null) { + return interfaceList; + } + + for (PsiClass psiClass: interfaces) { + String javaName = psiClass.getQualifiedName(); + if (javaName == null) { + throw new RuntimeException("I don't know what to do here... Is this even possible?"); + } + interfaceList.add(NameUtils.javaToSmaliType(javaName)); + } + + return interfaceList; + } + + @Nullable @Override public String getSourceFile() { + return null; + } + + @Nonnull @Override public Set getAnnotations() { + return ImmutableSet.of(); + } + + @Nonnull @Override public Iterable getStaticFields() { + return Iterables.transform( + Iterables.filter(Arrays.asList(psiClass.getFields()), new Predicate() { + @Override public boolean apply(PsiField psiField) { + PsiModifierList modifierList = psiField.getModifierList(); + if (modifierList == null) { + return false; + } + return modifierList.hasModifierProperty("static"); + } + }), + new Function() { + @Nullable @Override public Field apply(@Nullable PsiField psiField) { + return new SmalideaField(psiField); + } + }); + } + + @Nonnull @Override public Iterable getInstanceFields() { + return Iterables.transform( + Iterables.filter(Arrays.asList(psiClass.getFields()), new Predicate() { + @Override public boolean apply(PsiField psiField) { + PsiModifierList modifierList = psiField.getModifierList(); + if (modifierList == null) { + return true; + } + return !modifierList.hasModifierProperty("static"); + } + }), + new Function() { + @Nullable @Override public Field apply(@Nullable PsiField psiField) { + return new SmalideaField(psiField); + } + }); + } + + @Nonnull @Override public Iterable getFields() { + return Iterables.concat(getStaticFields(), getInstanceFields()); + } + + @Nonnull @Override public Iterable getDirectMethods() { + return Iterables.transform( + Iterables.filter( + Iterables.concat( + Arrays.asList(psiClass.getConstructors()), + Arrays.asList(psiClass.getMethods())), + new Predicate() { + @Override public boolean apply(PsiMethod psiMethod) { + PsiModifierList modifierList = psiMethod.getModifierList(); + return modifierList.hasModifierProperty("static") || + modifierList.hasModifierProperty("private") || + modifierList.hasModifierProperty("constructor"); + } + }), + new Function() { + @Nullable @Override public Method apply(PsiMethod psiMethod) { + return new SmalideaMethod(psiMethod); + } + }); + } + + @Nonnull @Override public Iterable getVirtualMethods() { + return Iterables.transform( + Iterables.filter(Arrays.asList(psiClass.getMethods()), new Predicate() { + @Override public boolean apply(PsiMethod psiMethod) { + PsiModifierList modifierList = psiMethod.getModifierList(); + return !modifierList.hasModifierProperty("static") && + !modifierList.hasModifierProperty("private") && + !modifierList.hasModifierProperty("constructor"); + } + }), + new Function() { + @Nullable @Override public Method apply(PsiMethod psiMethod) { + return new SmalideaMethod(psiMethod); + } + }); + } + + @Nonnull @Override public Iterable getMethods() { + return Iterables.concat(getDirectMethods(), getVirtualMethods()); + } +} diff --git a/smalidea/src/main/java/org/jf/smalidea/dexlib/SmalideaField.java b/smalidea/src/main/java/org/jf/smalidea/dexlib/SmalideaField.java new file mode 100644 index 00000000..bb022792 --- /dev/null +++ b/smalidea/src/main/java/org/jf/smalidea/dexlib/SmalideaField.java @@ -0,0 +1,121 @@ +/* + * 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.dexlib; + +import com.google.common.collect.ImmutableSet; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiField; +import com.intellij.psi.PsiModifierList; +import org.jf.dexlib2.AccessFlags; +import org.jf.dexlib2.base.reference.BaseFieldReference; +import org.jf.dexlib2.iface.Annotation; +import org.jf.dexlib2.iface.Field; +import org.jf.dexlib2.iface.value.EncodedValue; +import org.jf.smalidea.psi.impl.SmaliField; +import org.jf.smalidea.util.NameUtils; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Set; + +public class SmalideaField extends BaseFieldReference implements Field { + private final PsiField psiField; + + public SmalideaField(PsiField psiField) { + this.psiField = psiField; + } + + @Override public int getAccessFlags() { + if (psiField instanceof SmaliField) { + return ((SmaliField)psiField).getModifierList().getAccessFlags(); + } else { + int flags = 0; + PsiModifierList modifierList = psiField.getModifierList(); + if (modifierList == null) { + return flags; + } + if (modifierList.hasModifierProperty("public")) { + flags |= AccessFlags.PUBLIC.getValue(); + } else if (modifierList.hasModifierProperty("protected")) { + flags |= AccessFlags.PROTECTED.getValue(); + } else if (modifierList.hasModifierProperty("private")) { + flags |= AccessFlags.PRIVATE.getValue(); + } + + if (modifierList.hasModifierProperty("static")) { + flags |= AccessFlags.STATIC.getValue(); + } + + if (modifierList.hasModifierProperty("final")) { + flags |= AccessFlags.FINAL.getValue(); + } + + if (modifierList.hasModifierProperty("volatile")) { + flags |= AccessFlags.VOLATILE.getValue(); + } + // TODO: how do we tell if it's an enum? + + return flags; + } + } + + @Nonnull @Override public String getDefiningClass() { + PsiClass containingClass = psiField.getContainingClass(); + if (containingClass == null) { + throw new RuntimeException("I don't know what to do here... Is this even possible?"); + } + String javaName = containingClass.getQualifiedName(); + if (javaName == null) { + throw new RuntimeException("I don't know what to do here... Is this even possible?"); + } + return NameUtils.javaToSmaliType(javaName); + } + + @Nonnull @Override public String getName() { + return psiField.getNameIdentifier().getText(); + } + + @Nonnull @Override public String getType() { + String javaName = psiField.getType().getCanonicalText(); + return NameUtils.javaToSmaliType(javaName); + } + + @Nullable @Override public EncodedValue getInitialValue() { + // TODO: implement this. Not needed for method analysis + return null; + } + + @Nonnull @Override public Set getAnnotations() { + // TODO: implement this. Not needed for method analysis + return ImmutableSet.of(); + } +} diff --git a/smalidea/src/main/java/org/jf/smalidea/dexlib/SmalideaMethod.java b/smalidea/src/main/java/org/jf/smalidea/dexlib/SmalideaMethod.java index 3cfeebd6..a6c5c88a 100644 --- a/smalidea/src/main/java/org/jf/smalidea/dexlib/SmalideaMethod.java +++ b/smalidea/src/main/java/org/jf/smalidea/dexlib/SmalideaMethod.java @@ -36,9 +36,12 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiModifierList; import com.intellij.psi.PsiParameter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jf.dexlib2.AccessFlags; import org.jf.dexlib2.base.reference.BaseMethodReference; import org.jf.dexlib2.iface.*; import org.jf.dexlib2.iface.debug.DebugItem; @@ -47,7 +50,6 @@ import org.jf.smalidea.dexlib.instruction.SmalideaInstruction; import org.jf.smalidea.psi.impl.SmaliCatchStatement; import org.jf.smalidea.psi.impl.SmaliInstruction; import org.jf.smalidea.psi.impl.SmaliMethod; -import org.jf.smalidea.psi.impl.SmaliMethodParameter; import org.jf.smalidea.util.NameUtils; import javax.annotation.Nonnull; @@ -56,9 +58,9 @@ import java.util.List; import java.util.Set; public class SmalideaMethod extends BaseMethodReference implements Method { - private final SmaliMethod psiMethod; + private final PsiMethod psiMethod; - public SmalideaMethod(@NotNull SmaliMethod psiMethod) { + public SmalideaMethod(@NotNull PsiMethod psiMethod) { this.psiMethod = psiMethod; } @@ -71,21 +73,72 @@ public class SmalideaMethod extends BaseMethodReference implements Method { } @Nonnull @Override public List getParameters() { - SmaliMethodParameter[] parameters = psiMethod.getParameterList().getParameters(); + PsiParameter[] parameters = psiMethod.getParameterList().getParameters(); - return Lists.transform(Arrays.asList(parameters), new Function() { + return Lists.transform(Arrays.asList(parameters), new Function() { @Nullable @Override - public MethodParameter apply(@Nullable SmaliMethodParameter smaliParameter) { - if (smaliParameter == null) { + public MethodParameter apply(@Nullable PsiParameter psiParameter) { + if (psiParameter == null) { return null; } - return new SmalideaMethodParameter(smaliParameter); + return new SmalideaMethodParameter(psiParameter); } }); } @Override public int getAccessFlags() { - return psiMethod.getModifierList().getAccessFlags(); + if (psiMethod instanceof SmaliMethod) { + return ((SmaliMethod)psiMethod).getModifierList().getAccessFlags(); + } else { + int flags = 0; + PsiModifierList modifierList = psiMethod.getModifierList(); + if (modifierList.hasModifierProperty("public")) { + flags |= AccessFlags.PUBLIC.getValue(); + } else if (modifierList.hasModifierProperty("protected")) { + flags |= AccessFlags.PROTECTED.getValue(); + } else if (modifierList.hasModifierProperty("private")) { + flags |= AccessFlags.PRIVATE.getValue(); + } + + if (modifierList.hasModifierProperty("static")) { + flags |= AccessFlags.STATIC.getValue(); + } + + if (modifierList.hasModifierProperty("final")) { + flags |= AccessFlags.FINAL.getValue(); + } + + boolean isNative = false; + if (modifierList.hasModifierProperty("native")) { + flags |= AccessFlags.NATIVE.getValue(); + isNative = true; + } + + if (modifierList.hasModifierProperty("synchronized")) { + if (isNative) { + flags |= AccessFlags.SYNCHRONIZED.getValue(); + } else { + flags |= AccessFlags.DECLARED_SYNCHRONIZED.getValue(); + } + } + + if (psiMethod.isVarArgs()) { + flags |= AccessFlags.VARARGS.getValue(); + } + + if (modifierList.hasModifierProperty("abstract")) { + flags |= AccessFlags.ABSTRACT.getValue(); + } + + if (modifierList.hasModifierProperty("strictfp")) { + flags |= AccessFlags.STRICTFP.getValue(); + } + + if (psiMethod.isConstructor()) { + flags |= AccessFlags.CONSTRUCTOR.getValue(); + } + return flags; + } } @Nonnull @Override public Set getAnnotations() { @@ -94,44 +147,49 @@ public class SmalideaMethod extends BaseMethodReference implements Method { } @Nullable @Override public MethodImplementation getImplementation() { - List instructions = psiMethod.getInstructions(); - if (instructions.size() == 0) { - return null; + if (psiMethod instanceof SmaliMethod) { + final SmaliMethod smaliMethod = (SmaliMethod)this.psiMethod; + + List instructions = smaliMethod.getInstructions(); + if (instructions.size() == 0) { + return null; + } + + // TODO: cache this? + return new MethodImplementation() { + @Override public int getRegisterCount() { + return smaliMethod.getRegisterCount(); + } + + @Nonnull @Override public Iterable getInstructions() { + return Lists.transform(smaliMethod.getInstructions(), + new Function() { + @Override + public Instruction apply(SmaliInstruction smaliInstruction) { + return SmalideaInstruction.of(smaliInstruction); + } + }); + } + + @Nonnull @Override public List> getTryBlocks() { + return Lists.transform(smaliMethod.getCatchStatements(), + new Function>() { + @Override + public TryBlock apply( + SmaliCatchStatement smaliCatchStatement) { + assert smaliCatchStatement != null; + return new SmalideaTryBlock(smaliCatchStatement); + } + }); + } + + @Nonnull @Override public Iterable getDebugItems() { + // TODO: implement this + return ImmutableList.of(); + } + }; } - - // TODO: cache this? - return new MethodImplementation() { - @Override public int getRegisterCount() { - return psiMethod.getRegisterCount(); - } - - @Nonnull @Override public Iterable getInstructions() { - return Lists.transform(psiMethod.getInstructions(), - new Function() { - @Override - public Instruction apply(SmaliInstruction smaliInstruction) { - return SmalideaInstruction.of(smaliInstruction); - } - }); - } - - @Nonnull @Override public List> getTryBlocks() { - return Lists.transform(psiMethod.getCatchStatements(), - new Function>() { - @Override - public TryBlock apply( - SmaliCatchStatement smaliCatchStatement) { - assert smaliCatchStatement != null; - return new SmalideaTryBlock(smaliCatchStatement); - } - }); - } - - @Nonnull @Override public Iterable getDebugItems() { - // TODO: implement this - return ImmutableList.of(); - } - }; + return null; } @Nonnull @Override public String getName() { diff --git a/smalidea/src/main/java/org/jf/smalidea/dexlib/SmalideaMethodParameter.java b/smalidea/src/main/java/org/jf/smalidea/dexlib/SmalideaMethodParameter.java index 68e3aa58..e9e26a7c 100644 --- a/smalidea/src/main/java/org/jf/smalidea/dexlib/SmalideaMethodParameter.java +++ b/smalidea/src/main/java/org/jf/smalidea/dexlib/SmalideaMethodParameter.java @@ -32,19 +32,20 @@ package org.jf.smalidea.dexlib; import com.google.common.collect.ImmutableSet; +import com.intellij.psi.PsiParameter; import org.jetbrains.annotations.Nullable; import org.jf.dexlib2.base.BaseMethodParameter; import org.jf.dexlib2.iface.Annotation; -import org.jf.smalidea.psi.impl.SmaliMethodParameter; +import org.jf.smalidea.util.NameUtils; import org.jf.smalidea.util.StringUtils; import javax.annotation.Nonnull; import java.util.Set; public class SmalideaMethodParameter extends BaseMethodParameter { - private final SmaliMethodParameter psiParameter; + private final PsiParameter psiParameter; - public SmalideaMethodParameter(SmaliMethodParameter psiParameter) { + public SmalideaMethodParameter(PsiParameter psiParameter) { this.psiParameter = psiParameter; } @@ -58,6 +59,6 @@ public class SmalideaMethodParameter extends BaseMethodParameter { } @Nonnull @Override public String getType() { - return psiParameter.getTypeElement().getText(); + return NameUtils.javaToSmaliType(psiParameter.getType().getCanonicalText()); } } diff --git a/smalidea/src/main/java/org/jf/smalidea/dexlib/analysis/SmalideaClassProvider.java b/smalidea/src/main/java/org/jf/smalidea/dexlib/analysis/SmalideaClassProvider.java new file mode 100644 index 00000000..e886a290 --- /dev/null +++ b/smalidea/src/main/java/org/jf/smalidea/dexlib/analysis/SmalideaClassProvider.java @@ -0,0 +1,36 @@ +package org.jf.smalidea.dexlib.analysis; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.JavaPsiFacade; +import com.intellij.psi.PsiClass; +import com.intellij.psi.impl.ResolveScopeManager; +import org.jf.dexlib2.analysis.ClassProvider; +import org.jf.dexlib2.iface.ClassDef; +import org.jf.smalidea.dexlib.SmalideaClassDef; +import org.jf.smalidea.util.NameUtils; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SmalideaClassProvider implements ClassProvider { + private final Project project; + private final VirtualFile file; + + public SmalideaClassProvider(@Nonnull Project project, @Nonnull VirtualFile file) { + this.project = project; + this.file = file; + } + + @Nullable @Override public ClassDef getClassDef(String type) { + ResolveScopeManager manager = ResolveScopeManager.getInstance(project); + + JavaPsiFacade facade = JavaPsiFacade.getInstance(project); + PsiClass psiClass = facade.findClass(NameUtils.smaliToJavaType(type), + manager.getDefaultResolveScope(file)); + if (psiClass != null) { + return new SmalideaClassDef(psiClass); + } + return null; + } +} 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 b1ae85e7..085585bb 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 @@ -53,6 +53,7 @@ import org.jf.dexlib2.analysis.AnalysisException; import org.jf.dexlib2.analysis.ClassPath; import org.jf.dexlib2.analysis.MethodAnalyzer; import org.jf.smalidea.dexlib.SmalideaMethod; +import org.jf.smalidea.dexlib.analysis.SmalideaClassProvider; import org.jf.smalidea.psi.SmaliElementTypes; import org.jf.smalidea.psi.iface.SmaliModifierListOwner; import org.jf.smalidea.psi.stub.SmaliMethodStub; @@ -316,7 +317,8 @@ public class SmaliMethod extends SmaliStubBasedPsiElement if (!PsiTreeUtil.hasErrorElements(this)) { ClassPath classPath; try { - classPath = new ClassPath(); + classPath = new ClassPath( + new SmalideaClassProvider(getProject(), getContainingFile().getVirtualFile())); } catch (IOException ex) { throw new RuntimeException(ex); } diff --git a/smalidea/src/main/java/org/jf/smalidea/util/NameUtils.java b/smalidea/src/main/java/org/jf/smalidea/util/NameUtils.java index 6662b9e6..4a3cf293 100644 --- a/smalidea/src/main/java/org/jf/smalidea/util/NameUtils.java +++ b/smalidea/src/main/java/org/jf/smalidea/util/NameUtils.java @@ -163,6 +163,12 @@ public class NameUtils { } } return; + case 'U': + if (smaliType.equals("Ujava/lang/Object;")) { + dest.append("java.lang.Object"); + return; + } + // fall through default: throw new RuntimeException("Invalid smali type: " + smaliType); } diff --git a/smalidea/src/test/java/org/jf/smalidea/SmaliCodeFragmentFactoryTest.java b/smalidea/src/test/java/org/jf/smalidea/SmaliCodeFragmentFactoryTest.java index e987a397..c61d3e44 100644 --- a/smalidea/src/test/java/org/jf/smalidea/SmaliCodeFragmentFactoryTest.java +++ b/smalidea/src/test/java/org/jf/smalidea/SmaliCodeFragmentFactoryTest.java @@ -32,6 +32,7 @@ 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; @@ -44,11 +45,13 @@ 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.testFramework.fixtures.LightCodeInsightFixtureTestCase; +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; @@ -57,20 +60,20 @@ import org.junit.Assert; import java.util.HashSet; import java.util.List; -public class SmaliCodeFragmentFactoryTest extends LightCodeInsightFixtureTestCase { - private static final String testClass = +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" + + " const/4 v1, 0x2\n" + // 0 "\n" + " .line 179\n" + " if-nez p1, :cond_5\n" + "\n" + - " move v0, v1\n" + + " move v0, v1\n" + // 2 "\n" + " .line 185\n" + " :goto_4\n" + @@ -83,7 +86,7 @@ public class SmaliCodeFragmentFactoryTest extends LightCodeInsightFixtureTestCas " .line 183\n" + " sget-object v0, Lorg/jf/Penroser/PenroserApp;->random:Ljava/util/Random;\n" + "\n" + - " const/4 v1, 0x3\n" + + " const/4 v1, 0x3\n" + // 6 "\n" + " invoke-virtual {v0, v1}, Ljava/util/Random;->nextInt(I)I\n" + "\n" + @@ -103,7 +106,7 @@ public class SmaliCodeFragmentFactoryTest extends LightCodeInsightFixtureTestCas ".end method"; public void testCompletion() throws NoDataException { - SmaliFile smaliFile = (SmaliFile)myFixture.addFileToProject("my/pkg/blah.smali", testClass); + 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"}); @@ -112,6 +115,112 @@ public class SmaliCodeFragmentFactoryTest extends LightCodeInsightFixtureTestCas 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;->()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;->()V\n" + + "\n" + + " .line 14\n" + + " :goto_13\n" + + " sget-object 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;->()V\n" + + "\n" + + " return-object v0\n" + + ".end method\n"; + + public void testRegisterType() throws NoDataException { + SmaliFile smaliFile = (SmaliFile)configureByText(SmaliFileType.INSTANCE, + registerTypeTestText.replace("", "")); + + int refOffset = registerTypeTestText.indexOf(""); + + 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("", "")); + + int refOffset = modifiedText.indexOf(""); + + 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, @@ -138,6 +247,21 @@ public class SmaliCodeFragmentFactoryTest extends LightCodeInsightFixtureTestCas 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( @@ -147,4 +271,9 @@ public class SmaliCodeFragmentFactoryTest extends LightCodeInsightFixtureTestCas ((EditorImpl)editor).setCaretActive(); return editor; } + + @Override + protected Sdk getTestProjectJdk() { + return JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk(); + } }