/* * dex2jar - Tools to work with android .dex and java .class files * Copyright (c) 2009-2013 Panxiaobo * * 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 com.googlecode.d2j.map; import com.googlecode.d2j.DexConstants; import com.googlecode.d2j.util.Mapper; import com.googlecode.dex2jar.ir.ts.UniqueQueue; import java.util.*; public class InheritanceTree implements Mapper { Map<String, Clz> clzMap = new HashMap<>(); String from; boolean isLibrary; public InheritanceTree() { } public static void main(String... args) { InheritanceTree tree = new InheritanceTree(); Clz a = tree.addClz(0, "La;"); a.addMethod(0, "abc", new String[0], "V"); Clz b = tree.addClz(0, "Lb;"); b.addMethod(0, "abc", new String[0], "V"); Clz c = tree.addClz(0, "Lc;"); c.relateSuper("Ljava/lang/Object;"); c.relateInterface("La;"); c.relateInterface("Lb;"); tree.link(); } private static boolean isPrivate(int accessFlags) { return ((DexConstants.ACC_PRIVATE) & accessFlags) != 0; } private static boolean isStaticOrPrivate(int accessFlags) { return ((DexConstants.ACC_PRIVATE | DexConstants.ACC_STATIC) & accessFlags) != 0; } private static boolean isStaticOrPrivateOrFinal(int accessFlags) { return ((DexConstants.ACC_PRIVATE | DexConstants.ACC_STATIC | DexConstants.ACC_FINAL) & accessFlags) != 0; } public void updateFrom(String from, boolean isLibrary) { this.from = from; this.isLibrary = isLibrary; } public String mapClassName(String name) { Clz clz = clzMap.get(name); if (clz == null) { return null; } return clz.name.newValue; } public void recordClassRenameTo(String old, String newName) { Clz clz = clzMap.get(old); if (clz == null) { WARN("WARN: cant find class %s", old); return; } if (clz.name.noRename) { WARN("WARN: cant rename class %s"); return; } clz.name.newValue = newName; } public void recordMethodRenameTo(String owner, String oldName, String[] args, String ret, String newName) { Clz clz = clzMap.get(owner); if (clz == null) { WARN("WARN: cant find class %s", owner); return; } String key = toMethodKey(oldName, args, ret); Mtd mtd = clz.methods.get(key); if (mtd == null) { WARN("WARN: cant find method %s->%s", owner, key); return; } if (mtd.name.noRename && !oldName.equals(newName)) { WARN("WARN: cant rename method %s->%s to %s", owner, key, newName); return; } if (mtd.name.newValue == null) { mtd.name.newValue = newName; } else if (!newName.equals(mtd.name.newValue)) { WARN("WARN: cant rename method %s->%s to %s, pre rename to %s", owner, key, newName, mtd.name.newValue); } } public void recordFieldRenameTo(String owner, String oldName, String type, String newName) { Clz clz = clzMap.get(owner); if (clz == null) { WARN("WARN: cant find class %s", owner); return; } String key = toFieldKey(oldName, type); Fld fld = clz.fields.get(key); if (fld == null) { WARN("WARN: cant find field %s->%s", owner, key); return; } if (fld.name.noRename && !oldName.equals(newName)) { WARN("WARN: cant rename field %s->%s to %s", owner, key, newName); return; } if (fld.name.newValue == null) { fld.name.newValue = newName; } else if (!newName.equals(fld.name.newValue)) { WARN("WARN: cant rename field %s->%s to %s, pre rename to %s", owner, key, newName, fld.name.newValue); } } public String mapFieldName(String owner, String name, String type) { Clz clz = clzMap.get(owner); if (clz == null) { return null; } Fld fld = clz.fields.get(toFieldKey(name, type)); if (fld == null) { return null; } return fld.name.newValue; } public String mapFieldOwner(String owner, String name, String type) { Clz clz = clzMap.get(owner); if (clz == null) { return null; } Fld fld = clz.fields.get(toFieldKey(name, type)); if (fld == null) { return clz.name.newValue; } Name fldName = fld.owner.name; if (fldName.newValue == null) { return fldName.oldValue; } else { return fldName.newValue; } } public String mapMethodName(String owner, String name, String[] args, String ret) { Clz clz = clzMap.get(owner); if (clz == null) { return null; } Mtd mtd = null; if (args == null || ret == null) { for (Mtd m : clz.methods.values()) { if (m.args.length == 0 && m.name.oldValue.equals(name)) { mtd = m; break; } } } else { mtd = clz.methods.get(toMethodKey(name, args, ret)); } if (mtd == null) { return null; } return mtd.name.newValue; } public String mapMethodOwner(String owner, String name, String[] args, String ret) { Clz clz = clzMap.get(owner); if (clz == null) { return null; } Mtd mtd = null; if (args == null || ret == null) { for (Mtd m : clz.methods.values()) { if (m.args.length == 0 && m.name.oldValue.equals(name)) { mtd = m; break; } } } else { mtd = clz.methods.get(toMethodKey(name, args, ret)); } if (mtd == null) { return clz.name.newValue; } Name mtdName = mtd.owner.name; if (mtdName.newValue == null) { return mtdName.oldValue; } else { return mtdName.newValue; } } /** * @param accessFlags * the access flags of the class * @param name * the descriptor of the class * @return return null if the class is redefined */ Clz addClz(int accessFlags, String name) { Clz clz = getOrCreateClz(name); if (clz.stat != Stat.UNKNOWN) { if (clz.stat == Stat.LIBRARY && !isLibrary) { WARN("app class %s redefined, org %s, new %s, skiping.", name, clz.from, from); return null; } else { WARN("class %s is defined in %s, skip redefine in %s", name, clz.from, from); return null; } } clz.stat = isLibrary ? Stat.LIBRARY : Stat.APP; clz.accessFlags = accessFlags; clz.from = from; return clz; } public void link() { Queue<Clz> q = new UniqueQueue<>(); q.addAll(clzMap.values()); while (!q.isEmpty()) { Clz clz = q.poll(); for (Map.Entry<String, Mtd> e : clz.methods.entrySet()) { String key = e.getKey(); Mtd mtd = e.getValue(); Name name = mtd.name.trim(); mtd.name = name; if (name.oldValue.startsWith("<")) { // constructor, skip continue; } if (isPrivate(mtd.accessFlags)) { continue; } if (clz.children.size() > 0) { for (Clz child : clz.children) { Mtd childMtd = child.methods.get(key); if (childMtd != null) { if (isStaticOrPrivateOrFinal(childMtd.accessFlags)) { continue; } childMtd.name = merge(name, childMtd.name); } else { child.methods.put(key, mtd); q.add(child); } } } if (clz.impls.size() > 0) { for (Clz child : clz.impls) { Mtd childMtd = child.methods.get(key); if (childMtd != null) { childMtd.name = merge(name, childMtd.name); } else { child.methods.put(key, mtd); q.add(child); } } } } } q.addAll(clzMap.values()); while (!q.isEmpty()) { Clz clz = q.poll(); if (clz.fields.size() > 0) { for (Map.Entry<String, Fld> e : clz.fields.entrySet()) { String key = e.getKey(); Fld fld = e.getValue(); if (isPrivate(fld.accessFlags)) { // also copy static method to sub continue; } if (clz.children.size() > 0) { for (Clz child : clz.children) { if (!child.fields.containsKey(key)) { child.fields.put(key, fld); q.add(child); } } } } } } for (Clz clz : clzMap.values()) { if (clz.stat == Stat.UNKNOWN) { WARN("clz %s is unknow", clz.name); } boolean noRename = clz.stat == Stat.UNKNOWN || clz.stat == Stat.LIBRARY; clz.name.noRename = noRename; if (clz.methods.size() > 0) { for (Mtd mtd : clz.methods.values()) { Name name = mtd.name.trim(); mtd.name = name; if (noRename) { name.noRename = true; } } } if (noRename && clz.fields.size() > 0) { for (Fld fld : clz.fields.values()) { fld.name.noRename = true; } } // relationship is useless now clz.children = null; clz.impls = null; clz.superClz = null; clz.interfaces = null; } } private Name merge(Name name, Name childMtd) { childMtd = childMtd.trim(); if (childMtd != name) { childMtd.next = name; } return name; } private boolean isPrivateOrFinal(int accessFlags) { return ((DexConstants.ACC_PRIVATE | DexConstants.ACC_FINAL) & accessFlags) != 0; } private void WARN(String s, Object... args) { System.err.println(String.format(s, args)); } Clz getOrCreateClz(String name) { Clz clz = clzMap.get(name); if (clz == null) { clz = new Clz(name); clzMap.put(name, clz); } return clz; } public static String toFieldKey(String name, String type) { return name + ":" + type; } public static String toMethodKey(String name, String args[], String ret) { StringBuilder sb = new StringBuilder(); sb.append(name).append("("); for (String arg : args) { sb.append(arg); } sb.append(")").append(ret); return sb.toString(); } public enum Stat { UNKNOWN, LIBRARY, APP } public static class Name { final String oldValue; boolean noRename = false; String newValue; Name next; public Name(String name) { this.oldValue = name; } public String toString() { return oldValue; } public Name trim() { Name n = this; while (n.next != null) { n = n.next; } return n; } } public static class Fld { public int accessFlags; public Name name; public Clz owner; public String type; } public static class Mtd { public int accessFlags; public Name name; public Clz owner; public String ret; public String[] args; } public class Clz { public final Name name; public Stat stat = Stat.UNKNOWN; public String from; public int accessFlags; public Clz superClz; public Set<Clz> interfaces = new HashSet<>(); public Set<Clz> children = new HashSet<>(); public Set<Clz> impls = new HashSet<>(); public Map<String, Mtd> methods = new HashMap<>(); public Map<String, Fld> fields = new HashMap<>(); Clz(String name) { this.name = new Name(name); } public String toString() { return name.toString(); } public void relateSuper(String name) { Clz s = getOrCreateClz(name); superClz = s; s.children.add(this); } public void relateInterface(String itf) { Clz s = getOrCreateClz(itf); interfaces.add(s); s.impls.add(this); } public void addMethod(int accessFlags, String name, String args[], String ret) { String key = toMethodKey(name, args, ret); if (methods.containsKey(key)) { WARN("DUP method: %s in class %s, skiping.", key, this.name); return; } Mtd mtd = new Mtd(); mtd.owner = this; mtd.accessFlags = accessFlags; mtd.name = new Name(name); mtd.ret = ret; mtd.args = args; methods.put(key, mtd); } public void addField(int accessFlags, String name, String type) { String key = toFieldKey(name, type); if (fields.containsKey(key)) { WARN("DUP field: %s in class %s, skiping.", key, this.name); return; } Fld fld = new Fld(); fld.owner = this; fld.name = new Name(name); fld.accessFlags = accessFlags; fld.type = type; fields.put(key, fld); } } }