/* * Copyright 2016 HuntBugs contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package one.util.huntbugs.util; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import com.strobel.assembler.ir.Instruction; import com.strobel.assembler.ir.OpCode; import com.strobel.assembler.metadata.MethodBody; import com.strobel.assembler.metadata.MethodDefinition; import com.strobel.assembler.metadata.MethodReference; import com.strobel.assembler.metadata.ParameterDefinition; import com.strobel.assembler.metadata.TypeDefinition; import com.strobel.assembler.metadata.TypeReference; import one.util.huntbugs.warning.WarningAnnotation.MemberInfo; /** * @author Tagir Valeev * */ public class Methods { private static final Set<String> SERIALIZATION_METHODS = new HashSet<>(Arrays.asList("writeReplace", "readResolve", "readObject", "readObjectNoData", "writeObject")); public static boolean isEqualsMethod(MethodReference mr) { return mr.getName().equals("equals") && mr.getSignature().equals("(Ljava/lang/Object;)Z"); } public static boolean isHashCodeMethod(MethodReference mr) { return mr.getName().equals("hashCode") && mr.getSignature().equals("()I"); } public static boolean isGetClass(MethodReference mr) { return mr.getName().equals("getClass") && mr.getErasedSignature().equals("()Ljava/lang/Class;"); } public static boolean is(MethodReference mr, String internalTypeName, String methodName, String methodSig) { return mr.getName().equals(methodName) && mr.getErasedSignature().equals(methodSig) && mr.getDeclaringType().getInternalName().equals(internalTypeName); } public static boolean knownToThrow(MethodReference mr) { if (mr.getName().startsWith("assert") || mr.getName().startsWith("require") || mr.getDeclaringType().getSimpleName().equals("Assert")) return true; if (Types.isBoxed(mr.getDeclaringType()) && mr.getName().startsWith("parse")) return true; return false; } public static MethodDefinition findSuperMethod(MethodReference mr) { MethodDefinition md = mr.resolve(); if(md == null) return null; TypeDefinition td = md.getDeclaringType(); return findSuperMethod(td, new MemberInfo(resolveToBridge(md))); } public static Set<MethodDefinition> findSuperMethods(MethodReference mr) { MethodDefinition md = mr.resolve(); if(md == null) return null; TypeDefinition td = md.getDeclaringType(); Set<MethodDefinition> set = new HashSet<>(); collectSuperMethods(td, new MemberInfo(resolveToBridge(md)), set); return set; } private static void collectSuperMethods(TypeDefinition type, MemberInfo mi, Set<MethodDefinition> list) { TypeReference superType = type.getBaseType(); if(superType != null) { TypeDefinition superTd = superType.resolve(); if(superTd != null) { MethodDefinition result = findMethod(superTd, mi); if(result != null) list.add(result); else collectSuperMethods(superTd, mi, list); } } for(TypeReference iface : type.getExplicitInterfaces()) { TypeDefinition ifaceTd = iface.resolve(); if(ifaceTd != null) { MethodDefinition result = findMethod(ifaceTd, mi); if(result != null) list.add(result); else collectSuperMethods(ifaceTd, mi, list); } } } public static MethodDefinition resolveToBridge(MethodDefinition md) { if (md.isBridgeMethod()) { return md; } for (MethodDefinition candidate : md.getDeclaringType().getDeclaredMethods()) { if (candidate.getName().equals(md.getName()) && candidate.isBridgeMethod()) { List<ParameterDefinition> params = candidate.getParameters(); if (params.size() == md.getParameters().size()) { MethodBody body = candidate.getBody(); if (body != null) { for (Instruction instr : body.getInstructions()) { if (instr.getOperandCount() == 1) { Object operand = instr.getOperand(0); if (operand instanceof MethodReference) { MethodReference mr = (MethodReference) operand; if (mr.getName().equals(md.getName()) && mr.getErasedSignature().equals(md .getErasedSignature()) && mr.getDeclaringType().isEquivalentTo(md .getDeclaringType())) { return candidate; } } } } } } } } return md; } public static MethodDefinition findSuperMethod(TypeDefinition type, MemberInfo mi) { TypeReference superType = type.getBaseType(); if(superType != null) { TypeDefinition superTd = superType.resolve(); if(superTd != null) { MethodDefinition result = findMethod(superTd, mi); if(result != null) return result; result = findSuperMethod(superTd, mi); if(result != null) return result; } } for(TypeReference iface : type.getExplicitInterfaces()) { TypeDefinition ifaceTd = iface.resolve(); if(ifaceTd != null) { MethodDefinition result = findMethod(ifaceTd, mi); if(result != null) return result; result = findSuperMethod(ifaceTd, mi); if(result != null) return result; } } return null; } public static MethodDefinition findMethod(TypeDefinition td, MemberInfo mi) { if(td == null) return null; for(MethodDefinition decl : td.getDeclaredMethods()) { if(decl.getName().equals(mi.getName())) { String sig1 = decl.getErasedSignature(); String sig2 = mi.getSignature(); if(sig1 == sig2) return decl; if(sig1.substring(0, sig1.indexOf(')')).equals(sig2.substring(0, sig2.indexOf(')')))) return decl; } } return null; } public static boolean isMain(MethodDefinition md) { return md.getName().equals("main") && md.isPublic() && md.isStatic() && md.getErasedSignature().startsWith("([Ljava/lang/String;)"); } public static boolean isSideEffectFree(MethodReference mr) { if(isPure(mr)) return true; if(isEqualsMethod(mr) || isHashCodeMethod(mr)) return true; TypeReference tr = mr.getDeclaringType(); String sig = mr.getErasedSignature(); String name = mr.getName(); if(name.equals("toString") && sig.equals("()Ljava/lang/String;")) return true; switch(tr.getInternalName()) { case "java/util/Arrays": return name.equals("hashCode") || name.equals("equals") || name.equals("toString") || name.equals("binarySearch") || name.equals("stream") || name.equals("spliterator") || name.startsWith("deep") || name.startsWith("copyOf") || name.equals("asList"); case "java/lang/Object": return mr.isConstructor(); case "java/util/Collections": return name.equals("min") || name.equals("max") || name.startsWith("unmodifiable") || name.startsWith("synchronized") || name.startsWith("empty"); } if(Types.isCollection(tr)) { if(name.equals("contains") && sig.equals("(Ljava/lang/Object;)Z")) return true; if(name.equals("containsAll") && sig.equals("(Ljava/util/Collection;)Z")) return true; if(name.equals("isEmpty") && sig.equals("()Z")) return true; if(name.equals("size") && sig.equals("()I")) return true; if(Types.isInstance(tr, "java/util/List")) { if (name.equals("get") && sig.equals("(I)Ljava/lang/Object;") || name.equals("subList") && sig.equals("(II)Ljava/util/List;")) return true; } return false; } if(Types.isInstance(tr, "java/util/Map")) { if ((name.equals("containsKey") || name.equals("containsValue")) && sig.equals("(Ljava/lang/Object;)Z")) return true; if (name.equals("get") && sig.equals("(Ljava/lang/Object;)Ljava/lang/Object;")) return true; } return Types.isSideEffectFreeType(tr); } public static boolean isPure(MethodReference mr) { TypeReference tr = mr.getDeclaringType(); if(Types.isBoxed(tr) || tr.getInternalName().startsWith("java/time/")) return true; if(tr.getInternalName().equals("java/util/String")) return !mr.getName().equals("getChars"); if(tr.getInternalName().equals("java/lang/Math")) return !mr.getName().equals("random"); if(tr.getInternalName().equals("java/util/Objects")) return true; if(tr.getInternalName().equals("java/util/Optional")) return mr.getName().equals("get") || mr.getName().equals("orElse") || mr.getName().equals("isPresent"); return false; } public static boolean isThrower(MethodDefinition md) { MethodBody body = md.getBody(); if(body == null) return false; for(Instruction inst : body.getInstructions()) { if(inst.hasLabel() || inst.getOpCode() == OpCode.RETURN || inst.getOpCode() == OpCode.ARETURN) return false; if(inst.getOpCode() == OpCode.ATHROW) return true; } // Actually should not go here for valid bytecode return false; } public static boolean isSerializationMethod(MethodDefinition md) { return SERIALIZATION_METHODS.contains(md.getName()); } }