com.googlecode.d2j.dex.Dex2Asm Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle Show documentation
Show all versions of gradle Show documentation
fakeradnroid gradle builder
package com.googlecode.d2j.dex;
import java.util.*;
import com.googlecode.d2j.converter.Dex2IRConverter;
import org.objectweb.asm.*;
import org.objectweb.asm.tree.InnerClassNode;
import com.googlecode.d2j.*;
import com.googlecode.d2j.converter.IR2JConverter;
import com.googlecode.d2j.node.*;
import com.googlecode.dex2jar.ir.IrMethod;
import com.googlecode.dex2jar.ir.ts.*;
import com.googlecode.dex2jar.ir.ts.array.FillArrayTransformer;
public class Dex2Asm {
protected static class Clz {
public int access;
public Clz enclosingClass;
public Method enclosingMethod;
public String innerName;
public Set inners = null;
public final String name;
public Clz(String name) {
super();
this.name = name;
}
void addInner(Clz clz) {
if (inners == null) {
inners = new HashSet();
}
inners.add(clz);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Clz other = (Clz) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
public String toString() {
return "" + name;
}
}
protected static final int ACC_INTERFACE_ABSTRACT = (Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT);
private static final int NO_CODE_MASK = DexConstants.ACC_ABSTRACT | DexConstants.ACC_NATIVE
| DexConstants.ACC_ANNOTATION;
protected static final CleanLabel T_cleanLabel = new CleanLabel();
protected static final EndRemover T_endRemove = new EndRemover();
protected static final Ir2JRegAssignTransformer T_ir2jRegAssign = new Ir2JRegAssignTransformer();
protected static final NewTransformer T_new = new NewTransformer();
protected static final RemoveConstantFromSSA T_removeConst = new RemoveConstantFromSSA();
protected static final RemoveLocalFromSSA T_removeLocal = new RemoveLocalFromSSA();
protected static final ExceptionHandlerTrim T_trimEx = new ExceptionHandlerTrim();
protected static final TypeTransformer T_type = new TypeTransformer();
// protected static final TopologicalSort T_topologicalSort = new TopologicalSort();
protected static final DeadCodeTransformer T_deadCode = new DeadCodeTransformer();
protected static final FillArrayTransformer T_fillArray = new FillArrayTransformer();
protected static final AggTransformer T_agg = new AggTransformer();
protected static final UnSSATransformer T_unssa = new UnSSATransformer();
protected static final ZeroTransformer T_zero = new ZeroTransformer();
protected static final VoidInvokeTransformer T_voidInvoke = new VoidInvokeTransformer();
protected static final NpeTransformer T_npe = new NpeTransformer();
protected static final MultiArrayTransformer T_multiArray = new MultiArrayTransformer();
static private int clearClassAccess(boolean isInner, int access) {
if ((access & Opcodes.ACC_INTERFACE) == 0) { // issue 55
access |= Opcodes.ACC_SUPER;// 解决生成的class文件使用dx重新转换时使用的指令与原始指令不同的问题
}
// access in class has no acc_static or acc_private
access &= ~(Opcodes.ACC_STATIC | Opcodes.ACC_PRIVATE);
if (isInner && (access & Opcodes.ACC_PROTECTED) != 0) {// protected inner class are public
access &= ~Opcodes.ACC_PROTECTED;
access |= Opcodes.ACC_PUBLIC;
}
access &= ~DexConstants.ACC_DECLARED_SYNCHRONIZED; // clean ACC_DECLARED_SYNCHRONIZED
return access;
}
static private int clearInnerAccess(int access) {
access &= (~Opcodes.ACC_SUPER);// inner class attr has no acc_super
if (0 != (access & Opcodes.ACC_PRIVATE)) {// clear public/protected if it is private
access &= ~(Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED);
} else if (0 != (access & Opcodes.ACC_PROTECTED)) {// clear public if it is protected
access &= ~(Opcodes.ACC_PUBLIC);
}
return access;
}
protected static String toInternalName(DexType type) {
return toInternalName(type.desc);
}
protected static String toInternalName(String desc) {
// TODO without creating object
return Type.getType(desc).getInternalName();
}
public static void accept(DexAnnotationNode ann, ClassVisitor v) {
AnnotationVisitor av = v.visitAnnotation(ann.type, ann.visibility != Visibility.BUILD);
if (av != null) {
accept(ann.items, av);
av.visitEnd();
}
}
public static void accept(List anns, ClassVisitor cv) {
if (anns != null) {
for (DexAnnotationNode ann : anns) {
if (ann.visibility != Visibility.SYSTEM) {
accept(ann, cv);
}
}
}
}
public static void accept(List anns, FieldVisitor fv) {
if (anns != null) {
for (DexAnnotationNode ann : anns) {
if (ann.visibility != Visibility.SYSTEM) {
accept(ann, fv);
}
}
}
}
public static void accept(List anns, MethodVisitor mv) {
if (anns != null) {
for (DexAnnotationNode ann : anns) {
if (ann.visibility != Visibility.SYSTEM) {
accept(ann, mv);
}
}
}
}
public static void accept(DexAnnotationNode ann, MethodVisitor v) {
AnnotationVisitor av = v.visitAnnotation(ann.type, ann.visibility != Visibility.BUILD);
if (av != null) {
accept(ann.items, av);
av.visitEnd();
}
}
public static void acceptParameter(DexAnnotationNode ann, int index, MethodVisitor v) {
AnnotationVisitor av = v.visitParameterAnnotation(index, ann.type, ann.visibility != Visibility.BUILD);
if (av != null) {
accept(ann.items, av);
av.visitEnd();
}
}
public static void accept(DexAnnotationNode ann, FieldVisitor v) {
AnnotationVisitor av = v.visitAnnotation(ann.type, ann.visibility != Visibility.BUILD);
if (av != null) {
accept(ann.items, av);
av.visitEnd();
}
}
public static void accept(List items, AnnotationVisitor av) {
for (DexAnnotationNode.Item item : items) {
accept(av, item.name, item.value);
}
}
private static void accept(AnnotationVisitor dav, String name, Object o) {
if (o instanceof Object[]) {
AnnotationVisitor arrayVisitor = dav.visitArray(name);
if (arrayVisitor != null) {
Object[] array = (Object[]) o;
for (Object e : array) {
accept(arrayVisitor, null, e);
}
arrayVisitor.visitEnd();
}
} else if (o instanceof DexAnnotationNode) {
DexAnnotationNode ann = (DexAnnotationNode) o;
AnnotationVisitor av = dav.visitAnnotation(name, ann.type);
if (av != null) {
for (DexAnnotationNode.Item item : ann.items) {
accept(av, item.name, item.value);
}
av.visitEnd();
}
} else if (o instanceof Field) {
Field f = (Field) o;
dav.visitEnum(name, f.getType(), f.getName());
} else if (o instanceof DexType) {
dav.visit(name, Type.getType(((DexType) o).desc));
} else if (o instanceof Method) {
System.err.println("WARN: ignored method annotation value");
} else {
if (o == null) {
System.err.println("WARN: ignored null annotation value");
} else {
dav.visit(name, o);
}
}
}
private static MethodVisitor collectBasicMethodInfo(DexMethodNode methodNode, ClassVisitor cv) {
String xthrows[] = null;
String signature = null;
if (methodNode.anns != null) {
for (DexAnnotationNode ann : methodNode.anns) {
if (ann.visibility == Visibility.SYSTEM) {
switch (ann.type) {
case DexConstants.ANNOTATION_THROWS_TYPE: {
Object[] strs = (Object[]) findAnnotationAttribute(ann, "value");
if (strs != null) {
xthrows = new String[strs.length];
for (int i = 0; i < strs.length; i++) {
DexType type = (DexType) strs[i];
xthrows[i] = toInternalName(type);
}
}
}
break;
case DexConstants.ANNOTATION_SIGNATURE_TYPE: {
Object[] strs = (Object[]) findAnnotationAttribute(ann, "value");
if (strs != null) {
StringBuilder sb = new StringBuilder();
for (Object str : strs) {
sb.append(str);
}
signature = sb.toString();
}
}
break;
}
}
}
}
int access = methodNode.access;
// clear ACC_DECLARED_SYNCHRONIZED and ACC_CONSTRUCTOR from method flags
final int cleanFlag = ~((DexConstants.ACC_DECLARED_SYNCHRONIZED | DexConstants.ACC_CONSTRUCTOR));
access &= cleanFlag;
return cv.visitMethod(access, methodNode.method.getName(), methodNode.method.getDesc(), signature, xthrows);
}
protected static Map collectClzInfo(DexFileNode fileNode) {
Map classes = new HashMap<>();
for (DexClassNode classNode : fileNode.clzs) {
Clz clz = get(classes, classNode.className);
clz.access = (clz.access & ~ACC_INTERFACE_ABSTRACT) | classNode.access;
if (classNode.anns != null) {
for (DexAnnotationNode ann : classNode.anns) {
if (ann.visibility == Visibility.SYSTEM) {
switch (ann.type) {
case DexConstants.ANNOTATION_ENCLOSING_CLASS_TYPE: {
DexType type = (DexType) findAnnotationAttribute(ann, "value");
Clz enclosingClass = get(classes, type.desc);
clz.enclosingClass = enclosingClass;
// apply patch from ChaeHoon Lim,
// obfuscated code may declare itself as enclosing class
// which cause dex2jar to endless loop
//if(!clz.name.equals(clz.enclosingClass.name)) {
// enclosingClass.addInner(clz);
//}
enclosingClass.addInner(clz);
}
break;
case DexConstants.ANNOTATION_ENCLOSING_METHOD_TYPE: {
Method m = (Method) findAnnotationAttribute(ann, "value");
Clz enclosingClass = get(classes, m.getOwner());
clz.enclosingClass = enclosingClass;
clz.enclosingMethod = m;
enclosingClass.addInner(clz);
}
break;
case DexConstants.ANNOTATION_INNER_CLASS_TYPE: {
for (DexAnnotationNode.Item it : ann.items) {
if ("accessFlags".equals(it.name)) {
clz.access |= (Integer) it.value & ~ACC_INTERFACE_ABSTRACT;
} else if ("name".equals(it.name)) {
clz.innerName = (String) it.value;
}
}
}
break;
case DexConstants.ANNOTATION_MEMBER_CLASSES_TYPE: {
Object ts[] = (Object[]) findAnnotationAttribute(ann, "value");
for (Object v : ts) {
DexType type = (DexType) v;
Clz inner = get(classes, type.desc);
clz.addInner(inner);
inner.enclosingClass = clz;
}
}
break;
}
}
}
}
}
return classes;
}
public void convertClass(DexClassNode classNode, ClassVisitorFactory cvf, DexFileNode fileNode) {
convertClass(fileNode.dexVersion, classNode, cvf, collectClzInfo(fileNode));
}
public void convertClass(DexClassNode classNode, ClassVisitorFactory cvf) {
convertClass(DexConstants.DEX_035, classNode, cvf);
}
public void convertClass(int dexVersion, DexClassNode classNode, ClassVisitorFactory cvf) {
convertClass(dexVersion, classNode, cvf, new HashMap());
}
private static boolean isJavaIdentifier(String str) {
if (str.length() < 1) {
return false;
}
if (!Character.isJavaIdentifierStart(str.charAt(0))) {
return false;
}
for (int i = 1; i < str.length(); i++) {
if (!Character.isJavaIdentifierPart(str.charAt(i))) {
return false;
}
}
return true;
}
public void convertClass(DexClassNode classNode, ClassVisitorFactory cvf, Map classes) {
convertClass(DexConstants.DEX_035, classNode, cvf, classes);
}
public void convertClass(DexFileNode dfn, DexClassNode classNode, ClassVisitorFactory cvf, Map classes) {
convertClass(dfn.dexVersion, classNode, cvf, classes);
}
public void convertClass(int dexVersion, DexClassNode classNode, ClassVisitorFactory cvf, Map classes) {
ClassVisitor cv = cvf.create(toInternalName(classNode.className));
if (cv == null) {
return;
}
// the default value of static-final field are omitted by dex, fix it
DexFix.fixStaticFinalFieldValue(classNode);
String signature = null;
if (classNode.anns != null) {
for (DexAnnotationNode ann : classNode.anns) {
if (ann.visibility == Visibility.SYSTEM) {
switch (ann.type) {
case DexConstants.ANNOTATION_SIGNATURE_TYPE: {
Object[] strs = (Object[]) findAnnotationAttribute(ann, "value");
if (strs != null) {
StringBuilder sb = new StringBuilder();
for (Object str : strs) {
sb.append(str);
}
signature = sb.toString();
}
}
break;
}
}
}
}
String interfaceInterNames[] = null;
if (classNode.interfaceNames != null) {
interfaceInterNames = new String[classNode.interfaceNames.length];
for (int i = 0; i < classNode.interfaceNames.length; i++) {
interfaceInterNames[i] = toInternalName(classNode.interfaceNames[i]);
}
}
Clz clzInfo = classes.get(classNode.className);
int access = classNode.access;
boolean isInnerClass = false;
if (clzInfo != null) {
isInnerClass = clzInfo.enclosingClass != null || clzInfo.enclosingMethod != null;
}
access = clearClassAccess(isInnerClass, access);
int version = dexVersion >= DexConstants.DEX_037 ? Opcodes.V1_8 : Opcodes.V1_6;
cv.visit(version, access, toInternalName(classNode.className), signature,
classNode.superClass == null ? null : toInternalName(classNode.superClass), interfaceInterNames);
List innerClassNodes = new ArrayList(5);
if (clzInfo != null) {
searchInnerClass(clzInfo, innerClassNodes, classNode.className);
}
if (isInnerClass) {
// build Outer Clz
if (clzInfo.innerName == null) {// anonymous Innerclass
Method enclosingMethod = clzInfo.enclosingMethod;
if (enclosingMethod != null) {
cv.visitOuterClass(toInternalName(enclosingMethod.getOwner()), enclosingMethod.getName(),
enclosingMethod.getDesc());
} else {
Clz enclosingClass = clzInfo.enclosingClass;
cv.visitOuterClass(toInternalName(enclosingClass.name), null, null);
}
}
searchEnclosing(clzInfo, innerClassNodes);
}
Collections.sort(innerClassNodes, INNER_CLASS_NODE_COMPARATOR);
for (InnerClassNode icn : innerClassNodes) {
if (icn.innerName != null && !isJavaIdentifier(icn.innerName)) {
System.err.println("WARN: ignored invalid inner class name " + ", treat as anonymous inner class.");
icn.innerName = null;
icn.outerName = null;
}
icn.accept(cv);
}
accept(classNode.anns, cv);
if (classNode.fields != null) {
for (DexFieldNode fieldNode : classNode.fields) {
convertField(classNode, fieldNode, cv);
}
}
if (classNode.methods != null) {
for (DexMethodNode methodNode : classNode.methods) {
convertMethod(classNode, methodNode, cv);
}
}
cv.visitEnd();
}
public void convertCode(DexMethodNode methodNode, MethodVisitor mv) {
IrMethod irMethod = dex2ir(methodNode);
optimize(irMethod);
ir2j(irMethod, mv);
}
public void convertDex(DexFileNode fileNode, ClassVisitorFactory cvf) {
if (fileNode.clzs != null) {
Map classes = collectClzInfo(fileNode);
for (DexClassNode classNode : fileNode.clzs) {
convertClass(fileNode, classNode, cvf, classes);
}
}
}
public void convertField(DexClassNode classNode, DexFieldNode fieldNode, ClassVisitor cv) {
String signature = null;
if (fieldNode.anns != null) {
for (DexAnnotationNode ann : fieldNode.anns) {
if (ann.visibility == Visibility.SYSTEM) {
switch (ann.type) {
case DexConstants.ANNOTATION_SIGNATURE_TYPE: {
Object[] strs = (Object[]) findAnnotationAttribute(ann, "value");
if (strs != null) {
StringBuilder sb = new StringBuilder();
for (Object str : strs) {
sb.append(str);
}
signature = sb.toString();
}
}
break;
}
}
}
}
Object value = convertConstantValue(fieldNode.cst);
final int FieldCleanFlag = ~DexConstants.ACC_DECLARED_SYNCHRONIZED;
FieldVisitor fv = cv.visitField(fieldNode.access & FieldCleanFlag, fieldNode.field.getName(),
fieldNode.field.getType(), signature, value);
if (fv == null) {
return;
}
accept(fieldNode.anns, fv);
fv.visitEnd();
}
public static Object[] convertConstantValues(Object[] v) {
Object[] copy = Arrays.copyOf(v, v.length);
for (int i = 0; i < copy.length; i++) {
Object ele = copy[i];
ele = convertConstantValue(ele);
copy[i] = ele;
}
return copy;
}
public static Object convertConstantValue(Object ele) {
if (ele instanceof DexType) {
ele = Type.getType(((DexType) ele).desc);
} else if (ele instanceof MethodHandle) {
Handle h = null;
MethodHandle mh = (MethodHandle) ele;
switch (mh.getType()) {
case MethodHandle.INSTANCE_GET:
h = new Handle(Opcodes.H_GETFIELD, toInternalName(mh.getField().getOwner()), mh.getField().getName(), mh.getField().getType());
break;
case MethodHandle.INSTANCE_PUT:
h = new Handle(Opcodes.H_PUTFIELD, toInternalName(mh.getField().getOwner()), mh.getField().getName(), mh.getField().getType());
break;
case MethodHandle.STATIC_GET:
h = new Handle(Opcodes.H_GETFIELD, toInternalName(mh.getField().getOwner()), mh.getField().getName(), mh.getField().getType());
break;
case MethodHandle.STATIC_PUT:
h = new Handle(Opcodes.H_PUTFIELD, toInternalName(mh.getField().getOwner()), mh.getField().getName(), mh.getField().getType());
break;
case MethodHandle.INVOKE_INSTANCE:
h = new Handle(Opcodes.H_INVOKEVIRTUAL, toInternalName(mh.getMethod().getOwner()), mh.getMethod().getName(), mh.getMethod().getDesc());
break;
case MethodHandle.INVOKE_STATIC:
h = new Handle(Opcodes.H_INVOKESTATIC, toInternalName(mh.getMethod().getOwner()), mh.getMethod().getName(), mh.getMethod().getDesc());
break;
case MethodHandle.INVOKE_CONSTRUCTOR:
h = new Handle(Opcodes.H_NEWINVOKESPECIAL, toInternalName(mh.getMethod().getOwner()), mh.getMethod().getName(), mh.getMethod().getDesc());
break;
case MethodHandle.INVOKE_DIRECT:
h = new Handle(Opcodes.H_INVOKESPECIAL, toInternalName(mh.getMethod().getOwner()), mh.getMethod().getName(), mh.getMethod().getDesc());
break;
case MethodHandle.INVOKE_INTERFACE:
h = new Handle(Opcodes.H_INVOKEINTERFACE, toInternalName(mh.getMethod().getOwner()), mh.getMethod().getName(), mh.getMethod().getDesc());
break;
}
ele = h;
} else if (ele instanceof Proto) {
ele = Type.getMethodType(((Proto) ele).getDesc());
}
return ele;
}
public void convertMethod(DexClassNode classNode, DexMethodNode methodNode, ClassVisitor cv) {
MethodVisitor mv = collectBasicMethodInfo(methodNode, cv);
if (mv == null) {
return;
}
if (0 != (classNode.access & DexConstants.ACC_ANNOTATION)) { // its inside an annotation
Object defaultValue = null;
if (classNode.anns != null) {
for (DexAnnotationNode ann : classNode.anns) {
if (ann.visibility == Visibility.SYSTEM && ann.type.equals(DexConstants.ANNOTATION_DEFAULT_TYPE)) {
DexAnnotationNode node = (DexAnnotationNode) findAnnotationAttribute(ann, "value");
if (node != null) {
defaultValue = findAnnotationAttribute(node, methodNode.method.getName());
}
break;
}
}
}
if (defaultValue != null) {
AnnotationVisitor av = mv.visitAnnotationDefault();
if (av != null) {
accept(av, null, defaultValue);
av.visitEnd();
}
}
}
accept(methodNode.anns, mv);
if (methodNode.parameterAnns != null) {
for (int i = 0; i < methodNode.parameterAnns.length; i++) {
List anns = methodNode.parameterAnns[i];
if (anns != null) {
for (DexAnnotationNode ann : anns) {
if (ann.visibility != Visibility.SYSTEM) {
acceptParameter(ann, i, mv);
}
}
}
}
}
if ((NO_CODE_MASK & methodNode.access) == 0) { // has code
if (methodNode.codeNode != null) {
mv.visitCode();
convertCode(methodNode, mv);
}
}
mv.visitEnd();
}
public IrMethod dex2ir(DexMethodNode methodNode) {
return new Dex2IRConverter()
.convert(0 != (methodNode.access & DexConstants.ACC_STATIC), methodNode.method, methodNode.codeNode);
}
protected static Object findAnnotationAttribute(DexAnnotationNode ann, String name) {
for (DexAnnotationNode.Item item : ann.items) {
if (item.name.equals(name)) {
return item.value;
}
}
return null;
}
private static Clz get(Map classes, String name) {
Clz clz = classes.get(name);
if (clz == null) {
clz = new Clz(name);
classes.put(name, clz);
}
return clz;
}
public void ir2j(IrMethod irMethod, MethodVisitor mv) {
new IR2JConverter(false).convert(irMethod, mv);
mv.visitMaxs(-1, -1);
}
public void optimize(IrMethod irMethod) {
T_cleanLabel.transform(irMethod);
T_deadCode.transform(irMethod);
T_removeLocal.transform(irMethod);
T_removeConst.transform(irMethod);
T_zero.transform(irMethod);
if (T_npe.transformReportChanged(irMethod)) {
T_deadCode.transform(irMethod);
T_removeLocal.transform(irMethod);
T_removeConst.transform(irMethod);
}
T_new.transform(irMethod);
T_fillArray.transform(irMethod);
T_agg.transform(irMethod);
T_multiArray.transform(irMethod);
T_voidInvoke.transform(irMethod);
T_type.transform(irMethod);
T_unssa.transform(irMethod);
T_trimEx.transform(irMethod);
T_ir2jRegAssign.transform(irMethod);
}
/**
* For structure
*
*
* class A {
* class B {
* class WeAreHere {
* }
* }
* }
*
*
* this method will add
*
*
* InnerClass Outter
* A$B$WeAreHere A$B
* A$B A
*
*
* to WeAreHere.class
*
*/
private static void searchEnclosing(Clz clz, List innerClassNodes) {
Set visitedClz = new HashSet<>();
for (Clz p = clz; p != null; p = p.enclosingClass) {
if (!visitedClz.add(p)) { // prevent endless loop
break;
}
Clz enclosingClass = p.enclosingClass;
if (enclosingClass == null) {
break;
}
if (enclosingClass == clz) {
// enclosing itself, that is impossible
break;
}
int accessInInner = clearInnerAccess(p.access);
if (p.innerName != null) {// non-anonymous Innerclass
innerClassNodes.add(new InnerClassNode(toInternalName(p.name),
toInternalName(enclosingClass.name), p.innerName, accessInInner));
} else {// anonymous Innerclass
innerClassNodes.add(new InnerClassNode(toInternalName(p.name), null, null, accessInInner));
}
}
}
/**
* For structure
*
*
* class WeAreHere {
* class A {
* class B {
*
* }
* }
* }
*
*
* this method will add
*
*
* InnerClass Outter
* WeAreHere$A$B WeAreHere$A
* WeAreHere$A WeAreHere
*
*
* to WeAreHere.class
*
* @param clz
*/
private static void searchInnerClass(Clz clz, List innerClassNodes, String className) {
Set visited = new HashSet<>();
Stack stack = new Stack<>();
stack.push(clz);
while (!stack.empty()) {
clz = stack.pop();
if (visited.contains(clz)) {
continue;
} else {
visited.add(clz);
}
if (clz.inners != null) {
for (Clz inner : clz.inners) {
if (inner.innerName == null) {// anonymous Innerclass
innerClassNodes.add(new InnerClassNode(toInternalName(inner.name), null, null,
clearInnerAccess(inner.access)));
} else {// non-anonymous Innerclass
innerClassNodes.add(new InnerClassNode(toInternalName(inner.name), toInternalName(className),
inner.innerName, clearInnerAccess(inner.access)));
}
stack.push(inner);
}
}
}
}
private static final Comparator INNER_CLASS_NODE_COMPARATOR = new Comparator() {
@Override
public int compare(InnerClassNode o1, InnerClassNode o2) {
return o1.name.compareTo(o2.name);
}
};
}