package jadx.core.dex.nodes; import jadx.core.Consts; import jadx.core.codegen.CodeWriter; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.annotations.Annotation; import jadx.core.dex.attributes.nodes.JadxErrorAttr; import jadx.core.dex.attributes.nodes.LineAttrNode; import jadx.core.dex.attributes.nodes.SourceFileAttr; import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo.AFType; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.LiteralArg; import jadx.core.dex.instructions.args.PrimitiveType; import jadx.core.dex.nodes.parser.AnnotationsParser; import jadx.core.dex.nodes.parser.FieldValueAttr; import jadx.core.dex.nodes.parser.SignatureParser; import jadx.core.dex.nodes.parser.StaticValuesParser; import jadx.core.utils.exceptions.DecodeException; import jadx.core.utils.exceptions.JadxRuntimeException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.android.dex.ClassData; import com.android.dex.ClassData.Field; import com.android.dex.ClassData.Method; import com.android.dex.ClassDef; import com.android.dx.rop.code.AccessFlags; public class ClassNode extends LineAttrNode implements ILoadable { private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class); private final DexNode dex; private final ClassInfo clsInfo; private final AccessInfo accessFlags; private ArgType superClass; private List interfaces; private Map> genericMap; private final List methods; private final List fields; private Map constFields = Collections.emptyMap(); private List innerClasses = Collections.emptyList(); // store decompiled code private CodeWriter code; // store parent for inner classes or 'this' otherwise private ClassNode parentClass; private ProcessState state = ProcessState.NOT_LOADED; private final Set dependencies = new HashSet(); public ClassNode(DexNode dex, ClassDef cls) throws DecodeException { this.dex = dex; this.clsInfo = ClassInfo.fromDex(dex, cls.getTypeIndex()); try { if (cls.getSupertypeIndex() == DexNode.NO_INDEX) { this.superClass = null; } else { this.superClass = dex.getType(cls.getSupertypeIndex()); } this.interfaces = new ArrayList(cls.getInterfaces().length); for (short interfaceIdx : cls.getInterfaces()) { this.interfaces.add(dex.getType(interfaceIdx)); } if (cls.getClassDataOffset() != 0) { ClassData clsData = dex.readClassData(cls); int mthsCount = clsData.getDirectMethods().length + clsData.getVirtualMethods().length; int fieldsCount = clsData.getStaticFields().length + clsData.getInstanceFields().length; methods = new ArrayList(mthsCount); fields = new ArrayList(fieldsCount); for (Method mth : clsData.getDirectMethods()) { methods.add(new MethodNode(this, mth)); } for (Method mth : clsData.getVirtualMethods()) { methods.add(new MethodNode(this, mth)); } for (Field f : clsData.getStaticFields()) { fields.add(new FieldNode(this, f)); } loadStaticValues(cls, fields); for (Field f : clsData.getInstanceFields()) { fields.add(new FieldNode(this, f)); } } else { methods = Collections.emptyList(); fields = Collections.emptyList(); } loadAnnotations(cls); parseClassSignature(); setFieldsTypesFromSignature(); int sfIdx = cls.getSourceFileIndex(); if (sfIdx != DexNode.NO_INDEX) { String fileName = dex.getString(sfIdx); if (clsInfo != null && !clsInfo.getFullName().contains(fileName.replace(".java", "")) && !fileName.equals("SourceFile") && !fileName.equals("\"")) { this.addAttr(new SourceFileAttr(fileName)); LOG.debug("Class '{}' compiled from '{}'", this, fileName); } } // restore original access flags from dalvik annotation if present int accFlagsValue; Annotation a = getAnnotation(Consts.DALVIK_INNER_CLASS); if (a != null) { accFlagsValue = (Integer) a.getValues().get("accessFlags"); } else { accFlagsValue = cls.getAccessFlags(); } this.accessFlags = new AccessInfo(accFlagsValue, AFType.CLASS); } catch (Exception e) { throw new DecodeException("Error decode class: " + clsInfo, e); } } // empty synthetic class public ClassNode(DexNode dex, ClassInfo clsInfo) { this.dex = dex; this.clsInfo = clsInfo; this.interfaces = Collections.emptyList(); this.methods = Collections.emptyList(); this.fields = Collections.emptyList(); this.accessFlags = new AccessInfo(AccessFlags.ACC_PUBLIC | AccessFlags.ACC_SYNTHETIC, AFType.CLASS); this.parentClass = this; } private void loadAnnotations(ClassDef cls) { int offset = cls.getAnnotationsOffset(); if (offset != 0) { try { new AnnotationsParser(this).parse(offset); } catch (DecodeException e) { LOG.error("Error parsing annotations in {}", this, e); } } } private void loadStaticValues(ClassDef cls, List staticFields) throws DecodeException { for (FieldNode f : staticFields) { if (f.getAccessFlags().isFinal()) { f.addAttr(new FieldValueAttr(null)); } } int offset = cls.getStaticValuesOffset(); if (offset != 0) { StaticValuesParser parser = new StaticValuesParser(dex, dex.openSection(offset)); int count = parser.processFields(staticFields); constFields = new LinkedHashMap(count); for (FieldNode f : staticFields) { AccessInfo accFlags = f.getAccessFlags(); if (accFlags.isStatic() && accFlags.isFinal()) { FieldValueAttr fv = f.get(AType.FIELD_VALUE); if (fv != null && fv.getValue() != null) { if (accFlags.isPublic()) { dex.getConstFields().put(fv.getValue(), f); } constFields.put(fv.getValue(), f); } } } } } private void parseClassSignature() { SignatureParser sp = SignatureParser.fromNode(this); if (sp == null) { return; } try { // parse class generic map genericMap = sp.consumeGenericMap(); // parse super class signature superClass = sp.consumeType(); // parse interfaces signatures for (int i = 0; i < interfaces.size(); i++) { ArgType type = sp.consumeType(); if (type != null) { interfaces.set(i, type); } else { break; } } } catch (JadxRuntimeException e) { LOG.error("Class signature parse error: {}", this, e); } } private void setFieldsTypesFromSignature() { for (FieldNode field : fields) { SignatureParser sp = SignatureParser.fromNode(field); if (sp != null) { try { ArgType gType = sp.consumeType(); if (gType != null) { field.setType(gType); } } catch (JadxRuntimeException e) { LOG.error("Field signature parse error: {}", field, e); } } } } @Override public void load() { for (MethodNode mth : getMethods()) { try { mth.load(); } catch (Exception e) { LOG.error("Method load error: {}", mth, e); mth.addAttr(new JadxErrorAttr(e)); } } for (ClassNode innerCls : getInnerClasses()) { innerCls.load(); } } @Override public void unload() { for (MethodNode mth : getMethods()) { mth.unload(); } for (ClassNode innerCls : getInnerClasses()) { innerCls.unload(); } } @Nullable public ArgType getSuperClass() { return superClass; } public List getInterfaces() { return interfaces; } public Map> getGenericMap() { return genericMap; } public List getMethods() { return methods; } public List getFields() { return fields; } public FieldNode getConstField(Object obj) { return getConstField(obj, true); } public FieldNode getConstField(Object obj, boolean searchGlobal) { ClassNode cn = this; FieldNode field; do { field = cn.constFields.get(obj); } while (field == null && cn.clsInfo.getParentClass() != null && (cn = dex.resolveClass(cn.clsInfo.getParentClass())) != null); if (field == null && searchGlobal) { field = dex.getConstFields().get(obj); } if (obj instanceof Integer) { String str = dex.root().getResourcesNames().get(obj); if (str != null) { ResRefField resField = new ResRefField(dex, str.replace('/', '.')); if (field == null) { return resField; } if (!field.getName().equals(resField.getName())) { field = resField; } } } return field; } public FieldNode getConstFieldByLiteralArg(LiteralArg arg) { PrimitiveType type = arg.getType().getPrimitiveType(); if (type == null) { return null; } long literal = arg.getLiteral(); switch (type) { case BOOLEAN: return getConstField(literal == 1, false); case CHAR: return getConstField((char) literal, Math.abs(literal) > 10); case BYTE: return getConstField((byte) literal, Math.abs(literal) > 10); case SHORT: return getConstField((short) literal, Math.abs(literal) > 100); case INT: return getConstField((int) literal, Math.abs(literal) > 100); case LONG: return getConstField(literal, Math.abs(literal) > 1000); case FLOAT: float f = Float.intBitsToFloat((int) literal); return getConstField(f, f != 0.0); case DOUBLE: double d = Double.longBitsToDouble(literal); return getConstField(d, d != 0); } return null; } public FieldNode searchFieldById(int id) { return searchField(FieldInfo.fromDex(dex, id)); } public FieldNode searchField(FieldInfo field) { for (FieldNode f : fields) { if (f.getFieldInfo().equals(field)) { return f; } } return null; } @TestOnly public FieldNode searchFieldByName(String name) { for (FieldNode f : fields) { if (f.getName().equals(name)) { return f; } } return null; } public MethodNode searchMethod(MethodInfo mth) { for (MethodNode m : methods) { if (m.getMethodInfo().equals(mth)) { return m; } } return null; } public MethodNode searchMethodByName(String shortId) { for (MethodNode m : methods) { if (m.getMethodInfo().getShortId().equals(shortId)) { return m; } } return null; } public MethodNode searchMethodById(int id) { return searchMethodByName(MethodInfo.fromDex(dex, id).getShortId()); } public ClassNode getParentClass() { if (parentClass == null) { if (clsInfo.isInner()) { ClassNode parent = dex().resolveClass(clsInfo.getParentClass()); parent = parent == null ? this : parent; parentClass = parent; } else { parentClass = this; } } return parentClass; } public ClassNode getTopParentClass() { ClassNode parent = getParentClass(); return parent == this ? this : parent.getParentClass(); } public List getInnerClasses() { return innerClasses; } public void addInnerClass(ClassNode cls) { if (innerClasses.isEmpty()) { innerClasses = new ArrayList(3); } innerClasses.add(cls); } public boolean isEnum() { return getAccessFlags().isEnum() && getSuperClass() != null && getSuperClass().getObject().equals(ArgType.ENUM.getObject()); } public boolean isAnonymous() { return clsInfo.isInner() && clsInfo.getShortName().startsWith(Consts.ANONYMOUS_CLASS_PREFIX) && getDefaultConstructor() != null; } public MethodNode getDefaultConstructor() { for (MethodNode mth : methods) { if (mth.isDefaultConstructor()) { return mth; } } return null; } public AccessInfo getAccessFlags() { return accessFlags; } public DexNode dex() { return dex; } public String getRawName() { return clsInfo.getRawName(); } /** * Internal class info (don't use in code generation and external api). */ public ClassInfo getClassInfo() { return clsInfo; } /** * Class info for external usage (code generation and external api). */ public ClassInfo getAlias() { return clsInfo.getAlias(); } public String getShortName() { return clsInfo.getAlias().getShortName(); } public String getFullName() { return clsInfo.getAlias().getFullName(); } public String getPackage() { return clsInfo.getAlias().getPackage(); } public void setCode(CodeWriter code) { this.code = code; } public CodeWriter getCode() { return code; } public ProcessState getState() { return state; } public void setState(ProcessState state) { this.state = state; } public Set getDependencies() { return dependencies; } @Override public String toString() { return clsInfo.getFullName(); } }