1 /* 2 * Copyright 2014, 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.psi.impl; 33 34 import com.google.common.base.Supplier; 35 import com.google.common.base.Suppliers; 36 import com.google.common.collect.Maps; 37 import com.intellij.debugger.SourcePosition; 38 import com.intellij.lang.ASTNode; 39 import com.intellij.openapi.editor.Document; 40 import com.intellij.psi.*; 41 import com.intellij.psi.PsiModifier.ModifierConstant; 42 import com.intellij.psi.impl.PsiImplUtil; 43 import com.intellij.psi.impl.PsiSuperMethodImplUtil; 44 import com.intellij.psi.javadoc.PsiDocComment; 45 import com.intellij.psi.util.MethodSignature; 46 import com.intellij.psi.util.MethodSignatureBackedByPsiMethod; 47 import com.intellij.psi.util.PsiTreeUtil; 48 import com.intellij.util.IncorrectOperationException; 49 import org.jetbrains.annotations.NonNls; 50 import org.jetbrains.annotations.NotNull; 51 import org.jetbrains.annotations.Nullable; 52 import org.jf.dexlib2.analysis.AnalysisException; 53 import org.jf.dexlib2.analysis.ClassPath; 54 import org.jf.dexlib2.analysis.MethodAnalyzer; 55 import org.jf.smalidea.dexlib.SmalideaMethod; 56 import org.jf.smalidea.dexlib.analysis.SmalideaClassProvider; 57 import org.jf.smalidea.psi.SmaliElementTypes; 58 import org.jf.smalidea.psi.iface.SmaliModifierListOwner; 59 import org.jf.smalidea.psi.stub.SmaliMethodStub; 60 61 import java.io.IOException; 62 import java.util.Arrays; 63 import java.util.List; 64 import java.util.Map; 65 66 public class SmaliMethod extends SmaliStubBasedPsiElement<SmaliMethodStub> 67 implements PsiMethod, SmaliModifierListOwner, PsiAnnotationMethod { SmaliMethod(@otNull SmaliMethodStub stub)68 public SmaliMethod(@NotNull SmaliMethodStub stub) { 69 super(stub, SmaliElementTypes.METHOD); 70 } 71 SmaliMethod(@otNull ASTNode node)72 public SmaliMethod(@NotNull ASTNode node) { 73 super(node); 74 } 75 getName()76 @NotNull @Override public String getName() { 77 SmaliMethodStub stub = getStub(); 78 String name = null; 79 if (stub != null) { 80 name = stub.getName(); 81 } else { 82 SmaliMemberName nameIdentifier = getNameIdentifier(); 83 if (nameIdentifier != null) { 84 name = nameIdentifier.getText(); 85 } 86 } 87 if (name == null || name.isEmpty()) { 88 name = "<unnamed>"; 89 } 90 return name; 91 } 92 hasTypeParameters()93 @Override public boolean hasTypeParameters() { 94 // TODO: (generics) implement this 95 return false; 96 } 97 98 @NotNull getMethodPrototype()99 public SmaliMethodPrototype getMethodPrototype() { 100 return getRequiredStubOrPsiChild(SmaliElementTypes.METHOD_PROTOTYPE); 101 } 102 getReturnType()103 @Nullable @Override public PsiType getReturnType() { 104 if (isConstructor()) return null; 105 return getMethodPrototype().getReturnType(); 106 } 107 getReturnTypeElement()108 @Nullable @Override public PsiTypeElement getReturnTypeElement() { 109 if (isConstructor()) return null; 110 return getMethodPrototype().getReturnTypeElement(); 111 } 112 getParameterList()113 @NotNull @Override public SmaliMethodParamList getParameterList() { 114 return getMethodPrototype().getParameterList(); 115 } 116 getThrowsList()117 @NotNull @Override public SmaliThrowsList getThrowsList() { 118 return getRequiredStubOrPsiChild(SmaliElementTypes.THROWS_LIST); 119 } 120 getBody()121 @Nullable @Override public PsiCodeBlock getBody() { 122 // not applicable 123 return null; 124 } 125 getInstructions()126 @NotNull public List<SmaliInstruction> getInstructions() { 127 return findChildrenByType(SmaliElementTypes.INSTRUCTION); 128 } 129 getCatchStatements()130 @NotNull public List<SmaliCatchStatement> getCatchStatements() { 131 return Arrays.asList(findChildrenByClass(SmaliCatchStatement.class)); 132 } 133 getSourcePositionForCodeOffset(int offset)134 @Nullable public SourcePosition getSourcePositionForCodeOffset(int offset) { 135 for (SmaliInstruction instruction: getInstructions()) { 136 if (instruction.getOffset() >= offset) { 137 return SourcePosition.createFromElement(instruction); 138 } 139 } 140 return null; 141 } 142 getOffsetForLine(int line)143 public int getOffsetForLine(int line) { 144 PsiDocumentManager documentManager = PsiDocumentManager.getInstance(getProject()); 145 final Document document = documentManager.getDocument(getContainingFile()); 146 if (document == null) { 147 return -1; 148 } 149 150 for (final SmaliInstruction instruction: getInstructions()) { 151 int curLine = document.getLineNumber(instruction.getTextOffset()); 152 if (curLine >= line) { 153 return instruction.getOffset(); 154 } 155 } 156 return -1; 157 } 158 getRegisterCount()159 public int getRegisterCount() { 160 SmaliRegistersStatement registersStatement = findChildByClass(SmaliRegistersStatement.class); 161 if (registersStatement == null) { 162 return 0; 163 } 164 return registersStatement.getRegisterCount(); 165 } 166 getParameterRegisterCount()167 public int getParameterRegisterCount() { 168 int parameterRegisterCount = getMethodPrototype().getParameterList().getParameterRegisterCount(); 169 if (!isStatic()) { 170 parameterRegisterCount++; 171 } 172 return parameterRegisterCount; 173 } 174 getParameterStatements()175 @NotNull public SmaliParameterStatement[] getParameterStatements() { 176 return findChildrenByClass(SmaliParameterStatement.class); 177 } 178 isConstructor()179 @Override public boolean isConstructor() { 180 // TODO: should this return true for the class initializer? 181 return hasModifierProperty("constructor") && !hasModifierProperty("static"); 182 } 183 isStatic()184 public boolean isStatic() { 185 return hasModifierProperty("static"); 186 } 187 isVarArgs()188 @Override public boolean isVarArgs() { 189 return hasModifierProperty("varargs"); 190 } 191 getSignature(@otNull PsiSubstitutor substitutor)192 @NotNull @Override public MethodSignature getSignature(@NotNull PsiSubstitutor substitutor) { 193 return MethodSignatureBackedByPsiMethod.create(this, substitutor); 194 } 195 getNameIdentifier()196 @Nullable @Override public SmaliMemberName getNameIdentifier() { 197 return findChildByClass(SmaliMemberName.class); 198 } 199 findSuperMethods()200 @NotNull @Override public PsiMethod[] findSuperMethods() { 201 return PsiSuperMethodImplUtil.findSuperMethods(this); 202 } 203 findSuperMethods(boolean checkAccess)204 @NotNull @Override public PsiMethod[] findSuperMethods(boolean checkAccess) { 205 return PsiSuperMethodImplUtil.findSuperMethods(this, checkAccess); 206 } 207 findSuperMethods(PsiClass parentClass)208 @NotNull @Override public PsiMethod[] findSuperMethods(PsiClass parentClass) { 209 return PsiSuperMethodImplUtil.findSuperMethods(this, parentClass); 210 } 211 212 @NotNull @Override findSuperMethodSignaturesIncludingStatic(boolean checkAccess)213 public List<MethodSignatureBackedByPsiMethod> findSuperMethodSignaturesIncludingStatic(boolean checkAccess) { 214 return PsiSuperMethodImplUtil.findSuperMethodSignaturesIncludingStatic(this, checkAccess); 215 } 216 findDeepestSuperMethod()217 @Nullable @Override public PsiMethod findDeepestSuperMethod() { 218 return PsiSuperMethodImplUtil.findDeepestSuperMethod(this); 219 } 220 findDeepestSuperMethods()221 @NotNull @Override public PsiMethod[] findDeepestSuperMethods() { 222 return PsiSuperMethodImplUtil.findDeepestSuperMethods(this); 223 } 224 getModifierList()225 @NotNull @Override public SmaliModifierList getModifierList() { 226 return getRequiredStubOrPsiChild(SmaliElementTypes.MODIFIER_LIST); 227 } 228 setName(@onNls @otNull String name)229 @Override public PsiElement setName(@NonNls @NotNull String name) throws IncorrectOperationException { 230 SmaliMemberName smaliMemberName = getNameIdentifier(); 231 if (smaliMemberName == null) { 232 throw new IncorrectOperationException(); 233 } 234 smaliMemberName.setName(name); 235 return this; 236 } 237 getHierarchicalMethodSignature()238 @NotNull @Override public HierarchicalMethodSignature getHierarchicalMethodSignature() { 239 return PsiSuperMethodImplUtil.getHierarchicalMethodSignature(this); 240 } 241 getDocComment()242 @Nullable @Override public PsiDocComment getDocComment() { 243 // not applicable 244 return null; 245 } 246 isDeprecated()247 @Override public boolean isDeprecated() { 248 return PsiImplUtil.isDeprecatedByAnnotation(this); 249 } 250 getTypeParameterList()251 @Nullable @Override public PsiTypeParameterList getTypeParameterList() { 252 // TODO: (generics) implement this 253 return null; 254 } 255 getTypeParameters()256 @NotNull @Override public PsiTypeParameter[] getTypeParameters() { 257 // TODO: (generics) implement this 258 return new PsiTypeParameter[0]; 259 } 260 getContainingClass()261 @Nullable @Override public SmaliClass getContainingClass() { 262 PsiElement parent = getParentByStub(); 263 if (parent instanceof SmaliClass) { 264 return (SmaliClass) parent; 265 } 266 return null; 267 } 268 hasModifierProperty(@odifierConstant @onNls @otNull String name)269 @Override public boolean hasModifierProperty(@ModifierConstant @NonNls @NotNull String name) { 270 return getModifierList().hasModifierProperty(name); 271 } 272 getAnnotations()273 @NotNull @Override public SmaliAnnotation[] getAnnotations() { 274 return getStubOrPsiChildren(SmaliElementTypes.ANNOTATION, new SmaliAnnotation[0]); 275 } 276 getApplicableAnnotations()277 @NotNull @Override public SmaliAnnotation[] getApplicableAnnotations() { 278 return getAnnotations(); 279 } 280 findAnnotation(@otNull @onNls String qualifiedName)281 @Nullable @Override public SmaliAnnotation findAnnotation(@NotNull @NonNls String qualifiedName) { 282 for (SmaliAnnotation annotation: getAnnotations()) { 283 if (qualifiedName.equals(annotation.getQualifiedName())) { 284 return annotation; 285 } 286 } 287 return null; 288 } 289 addAnnotation(@otNull @onNls String qualifiedName)290 @NotNull @Override public SmaliAnnotation addAnnotation(@NotNull @NonNls String qualifiedName) { 291 // TODO: implement this 292 return null; 293 } 294 295 private final Supplier<Map<String, SmaliLabel>> labelMap = Suppliers.memoize( 296 new Supplier<Map<String, SmaliLabel>>() { 297 @Override public Map<String, SmaliLabel> get() { 298 Map<String, SmaliLabel> labelMap = Maps.newHashMap(); 299 for (SmaliLabel label: findChildrenByClass(SmaliLabel.class)) { 300 if (!labelMap.containsKey(label.getText())) { 301 labelMap.put(label.getText(), label); 302 } 303 } 304 return labelMap; 305 } 306 }); 307 getLabel(String name)308 @Nullable public SmaliLabel getLabel(String name) { 309 return labelMap.get().get(name); 310 } 311 312 private MethodAnalyzer methodAnalyzer = null; 313 314 @Nullable getMethodAnalyzer()315 public MethodAnalyzer getMethodAnalyzer() { 316 if (methodAnalyzer == null) { 317 if (!PsiTreeUtil.hasErrorElements(this)) { 318 ClassPath classPath; 319 try { 320 classPath = new ClassPath( 321 new SmalideaClassProvider(getProject(), getContainingFile().getVirtualFile())); 322 } catch (IOException ex) { 323 throw new RuntimeException(ex); 324 } 325 326 try { 327 methodAnalyzer = new MethodAnalyzer(classPath, new SmalideaMethod(SmaliMethod.this), null, false); 328 } catch (AnalysisException ex) { 329 methodAnalyzer = null; 330 } 331 } 332 } 333 return methodAnalyzer; 334 } 335 subtreeChanged()336 @Override public void subtreeChanged() { 337 super.subtreeChanged(); 338 methodAnalyzer = null; 339 } 340 getTextOffset()341 @Override public int getTextOffset() { 342 SmaliMemberName smaliMemberName = getNameIdentifier(); 343 if (smaliMemberName != null) { 344 return smaliMemberName.getTextOffset(); 345 } 346 return super.getTextOffset(); 347 } 348 getDefaultValue()349 @Nullable @Override public PsiAnnotationMemberValue getDefaultValue() { 350 SmaliClass containingClass = getContainingClass(); 351 if (containingClass == null || !containingClass.isAnnotationType()) { 352 return null; 353 } 354 355 for (SmaliAnnotation annotation: containingClass.getAnnotations()) { 356 String annotationType = annotation.getQualifiedName(); 357 if (annotationType == null) { 358 continue; 359 } 360 if (annotationType.equals("dalvik.annotation.AnnotationDefault")) { 361 PsiAnnotationMemberValue value = annotation.findAttributeValue("value"); 362 if (!(value instanceof SmaliAnnotation)) { 363 return null; 364 } 365 SmaliAnnotation valueSubAnnotation = (SmaliAnnotation)value; 366 return valueSubAnnotation.findAttributeValue(getName()); 367 } 368 } 369 return null; 370 } 371 } 372