ClassNode.java 17.2 KB
Newer Older
S
Skylot 已提交
1 2
package jadx.core.dex.nodes;

3 4 5
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
6
import java.util.LinkedHashSet;
7 8
import java.util.List;
import java.util.Map;
9
import java.util.Set;
10
import java.util.function.Consumer;
11
import java.util.stream.Collectors;
12

13
import org.jetbrains.annotations.NotNull;
14 15 16 17
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

18
import jadx.api.ICodeCache;
S
Skylot 已提交
19
import jadx.api.ICodeInfo;
20 21 22
import jadx.api.plugins.input.data.IClassData;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.annotations.IAnnotation;
S
Skylot 已提交
23
import jadx.core.Consts;
S
Skylot 已提交
24
import jadx.core.ProcessClass;
25
import jadx.core.dex.attributes.AFlag;
26
import jadx.core.dex.attributes.annotations.AnnotationsList;
S
Skylot 已提交
27 28
import jadx.core.dex.attributes.fldinit.FieldInitAttr;
import jadx.core.dex.attributes.fldinit.FieldInitConstAttr;
29
import jadx.core.dex.attributes.nodes.NotificationAttrNode;
S
Skylot 已提交
30
import jadx.core.dex.attributes.nodes.SourceFileAttr;
S
Skylot 已提交
31 32 33 34 35 36
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;
1
13.beta2 已提交
37
import jadx.core.dex.instructions.args.LiteralArg;
38
import jadx.core.dex.visitors.ProcessAnonymous;
39
import jadx.core.utils.Utils;
S
Skylot 已提交
40
import jadx.core.utils.exceptions.JadxRuntimeException;
S
Skylot 已提交
41

S
Skylot 已提交
42
import static jadx.core.dex.nodes.ProcessState.LOADED;
43
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
44
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
45

46
public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable<ClassNode> {
S
Skylot 已提交
47
	private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
S
Skylot 已提交
48

49
	private final RootNode root;
50
	private final IClassData clsData;
51

S
Skylot 已提交
52
	private final ClassInfo clsInfo;
53
	private AccessInfo accessFlags;
S
Skylot 已提交
54 55
	private ArgType superClass;
	private List<ArgType> interfaces;
56
	private List<ArgType> generics = Collections.emptyList();
S
Skylot 已提交
57

58 59
	private List<MethodNode> methods;
	private List<FieldNode> fields;
60
	private List<ClassNode> innerClasses = Collections.emptyList();
S
Skylot 已提交
61

62 63
	private List<ClassNode> inlinedClasses = Collections.emptyList();

64 65
	// store smali
	private String smali;
66 67
	// store parent for inner classes or 'this' otherwise
	private ClassNode parentClass;
S
Skylot 已提交
68

S
Skylot 已提交
69
	private volatile ProcessState state = ProcessState.NOT_LOADED;
70
	private LoadStage loadStage = LoadStage.NONE;
71 72

	/** Top level classes used in this class (only for top level classes, empty for inners) */
S
Skylot 已提交
73
	private List<ClassNode> dependencies = Collections.emptyList();
74 75 76 77
	/** Classes which uses this class */
	private List<ClassNode> useIn = Collections.emptyList();
	/** Methods which uses this class (by instructions only, definition is excluded) */
	private List<MethodNode> useInMth = Collections.emptyList();
78

S
Skylot 已提交
79 80 81
	// cache maps
	private Map<MethodInfo, MethodNode> mthInfoMap = Collections.emptyMap();

82 83 84
	public ClassNode(RootNode root, IClassData cls) {
		this.root = root;
		this.clsInfo = ClassInfo.fromType(root, ArgType.object(cls.getType()));
85
		this.clsData = cls.copy();
86
		initialLoad(clsData);
87 88
	}

S
Skylot 已提交
89
	private void initialLoad(IClassData cls) {
S
Skylot 已提交
90
		try {
91 92
			String superType = cls.getSuperType();
			if (superType == null) {
93 94 95 96
				// only java.lang.Object don't have super class
				if (!clsInfo.getType().getObject().equals(Consts.CLASS_OBJECT)) {
					throw new JadxRuntimeException("No super class in " + clsInfo.getType());
				}
97
				this.superClass = null;
S
Skylot 已提交
98
			} else {
99
				this.superClass = ArgType.object(superType);
S
Skylot 已提交
100
			}
101
			this.interfaces = Utils.collectionMap(cls.getInterfacesTypes(), ArgType::object);
S
Skylot 已提交
102

103 104 105 106 107
			methods = new ArrayList<>();
			fields = new ArrayList<>();
			cls.visitFieldsAndMethods(
					fld -> fields.add(FieldNode.build(this, fld)),
					mth -> methods.add(MethodNode.build(this, mth)));
S
Skylot 已提交
108

109 110
			AnnotationsList.attach(this, cls.getAnnotations());
			loadStaticValues(cls, fields);
111
			initAccessFlags(cls);
S
Skylot 已提交
112

113
			addSourceFilenameAttr(cls.getSourceFile());
S
Skylot 已提交
114
			buildCache();
S
Skylot 已提交
115
		} catch (Exception e) {
116
			throw new JadxRuntimeException("Error decode class: " + clsInfo, e);
S
Skylot 已提交
117 118 119
		}
	}

120 121 122 123 124 125
	public void updateGenericClsData(ArgType superClass, List<ArgType> interfaces, List<ArgType> generics) {
		this.superClass = superClass;
		this.interfaces = interfaces;
		this.generics = generics;
	}

126 127 128
	/**
	 * Restore original access flags from Dalvik annotation if present
	 */
129
	private void initAccessFlags(IClassData cls) {
130
		int accFlagsValue;
131
		IAnnotation a = getAnnotation(Consts.DALVIK_INNER_CLASS);
132
		if (a != null) {
133
			accFlagsValue = (Integer) a.getValues().get("accessFlags").getValue();
134 135 136 137 138 139
		} else {
			accFlagsValue = cls.getAccessFlags();
		}
		this.accessFlags = new AccessInfo(accFlagsValue, AFType.CLASS);
	}

140 141 142 143 144 145 146 147 148 149
	public static ClassNode addSyntheticClass(RootNode root, String name, int accessFlags) {
		ClassNode cls = new ClassNode(root, name, accessFlags);
		cls.add(AFlag.SYNTHETIC);
		cls.setState(ProcessState.PROCESS_COMPLETE);
		root.addClassNode(cls);
		return cls;
	}

	// Create empty class
	private ClassNode(RootNode root, String name, int accessFlags) {
150
		this.root = root;
151
		this.clsData = null;
152
		this.clsInfo = ClassInfo.fromName(root, name);
153 154 155 156
		this.interfaces = new ArrayList<>();
		this.methods = new ArrayList<>();
		this.fields = new ArrayList<>();
		this.accessFlags = new AccessInfo(accessFlags, AFType.CLASS);
157 158 159
		this.parentClass = this;
	}

160 161 162
	private void loadStaticValues(IClassData cls, List<FieldNode> fields) {
		if (fields.isEmpty()) {
			return;
S
Skylot 已提交
163
		}
164
		List<FieldNode> staticFields = fields.stream().filter(FieldNode::isStatic).collect(Collectors.toList());
S
Skylot 已提交
165 166
		for (FieldNode f : staticFields) {
			if (f.getAccessFlags().isFinal()) {
167
				// incorrect initialization will be removed if assign found in constructor
S
Skylot 已提交
168
				f.addAttr(FieldInitConstAttr.NULL_VALUE);
S
Skylot 已提交
169 170
			}
		}
171 172 173 174 175 176 177 178 179 180 181 182 183
		try {
			List<EncodedValue> values = cls.getStaticFieldInitValues();
			int count = values.size();
			if (count == 0 || count > staticFields.size()) {
				return;
			}
			for (int i = 0; i < count; i++) {
				staticFields.get(i).addAttr(FieldInitAttr.constValue(values.get(i)));
			}
			// process const fields
			root().getConstValues().processConstFields(this, staticFields);
		} catch (Exception e) {
			this.addWarnComment("Failed to load initial values for static fields", e);
184
		}
S
Skylot 已提交
185 186
	}

187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
	private void addSourceFilenameAttr(String fileName) {
		if (fileName == null) {
			return;
		}
		if (fileName.endsWith(".java")) {
			fileName = fileName.substring(0, fileName.length() - 5);
		}
		if (fileName.isEmpty()
				|| fileName.equals("SourceFile")
				|| fileName.equals("\"")) {
			return;
		}
		if (clsInfo != null) {
			String name = clsInfo.getShortName();
			if (fileName.equals(name)) {
				return;
			}
			if (fileName.contains("$")
205
					&& fileName.endsWith('$' + name)) {
206 207
				return;
			}
S
Skylot 已提交
208 209
			ClassInfo parentCls = clsInfo.getTopParentClass();
			if (parentCls != null && fileName.equals(parentCls.getShortName())) {
S
Skylot 已提交
210 211
				return;
			}
212 213 214 215
		}
		this.addAttr(new SourceFileAttr(fileName));
	}

216 217
	public void ensureProcessed() {
		ClassNode topClass = getTopParentClass();
218 219
		ProcessState state = topClass.getState();
		if (state != PROCESS_COMPLETE) {
220
			throw new JadxRuntimeException("Expected class to be processed at this point,"
221
					+ " class: " + topClass + ", state: " + state);
222
		}
S
Skylot 已提交
223 224
	}

225 226 227 228 229 230 231 232
	public ICodeInfo decompile() {
		return decompile(true);
	}

	public ICodeInfo getCode() {
		return decompile(true);
	}

233 234
	public ICodeInfo reloadCode() {
		add(AFlag.CLASS_DEEP_RELOAD);
235 236 237
		return decompile(false);
	}

S
Skylot 已提交
238
	public void deepUnload() {
239
		if (clsData == null) {
S
Skylot 已提交
240 241 242
			// manually added class
			return;
		}
243
		clearAttributes();
244
		root().getConstValues().removeForClass(this);
245
		initialLoad(clsData);
246 247 248 249 250
		ProcessAnonymous.runForClass(this);

		for (ClassNode innerClass : innerClasses) {
			innerClass.deepUnload();
		}
251 252 253
	}

	private synchronized ICodeInfo decompile(boolean searchInCache) {
254 255 256
		ICodeCache codeCache = root().getCodeCache();
		ClassNode topParentClass = getTopParentClass();
		String clsRawName = topParentClass.getRawName();
257 258
		if (searchInCache) {
			ICodeInfo code = codeCache.get(clsRawName);
259
			if (code != null && code != ICodeInfo.EMPTY) {
260 261
				return code;
			}
S
Skylot 已提交
262
		}
263 264
		ICodeInfo codeInfo = ProcessClass.generateCode(topParentClass);
		codeCache.add(clsRawName, codeInfo);
S
Skylot 已提交
265 266 267
		return codeInfo;
	}

S
Skylot 已提交
268
	@Override
S
Skylot 已提交
269
	public void load() {
S
Skylot 已提交
270
		for (MethodNode mth : getMethods()) {
S
Skylot 已提交
271 272
			try {
				mth.load();
273
			} catch (Exception e) {
274
				mth.addError("Method load error", e);
S
Skylot 已提交
275
			}
S
Skylot 已提交
276 277 278 279
		}
		for (ClassNode innerCls : getInnerClasses()) {
			innerCls.load();
		}
S
Skylot 已提交
280
		setState(LOADED);
S
Skylot 已提交
281 282 283 284
	}

	@Override
	public void unload() {
285 286 287
		if (state == NOT_LOADED) {
			return;
		}
288 289 290 291
		methods.forEach(MethodNode::unload);
		innerClasses.forEach(ClassNode::unload);
		fields.forEach(FieldNode::unloadAttributes);
		unloadAttributes();
292
		setState(NOT_LOADED);
293
		this.loadStage = LoadStage.NONE;
294
		this.smali = null;
S
Skylot 已提交
295 296
	}

S
Skylot 已提交
297
	private void buildCache() {
S
Skylot 已提交
298
		mthInfoMap = new HashMap<>(methods.size());
S
Skylot 已提交
299 300 301 302 303
		for (MethodNode mth : methods) {
			mthInfoMap.put(mth.getMethodInfo(), mth);
		}
	}

S
Skylot 已提交
304 305
	@Nullable
	public ArgType getSuperClass() {
S
Skylot 已提交
306 307 308
		return superClass;
	}

S
Skylot 已提交
309
	public List<ArgType> getInterfaces() {
S
Skylot 已提交
310 311 312
		return interfaces;
	}

313
	public List<ArgType> getGenericTypeParameters() {
314
		return generics;
315 316
	}

317 318 319 320 321 322 323 324
	public ArgType getType() {
		ArgType clsType = clsInfo.getType();
		if (Utils.notEmpty(generics)) {
			return ArgType.generic(clsType, generics);
		}
		return clsType;
	}

S
Skylot 已提交
325 326 327 328 329 330 331 332
	public List<MethodNode> getMethods() {
		return methods;
	}

	public List<FieldNode> getFields() {
		return fields;
	}

333 334 335 336
	public void addField(FieldNode fld) {
		fields.add(fld);
	}

337 338 339 340
	public FieldNode getConstField(Object obj) {
		return getConstField(obj, true);
	}

341
	@Nullable
342
	public FieldNode getConstField(Object obj, boolean searchGlobal) {
343
		return root().getConstValues().getConstField(this, obj, searchGlobal);
344 345
	}

346
	@Nullable
1
13.beta2 已提交
347
	public FieldNode getConstFieldByLiteralArg(LiteralArg arg) {
348
		return root().getConstValues().getConstFieldByLiteralArg(this, arg);
1
13.beta2 已提交
349 350
	}

351
	public FieldNode searchField(FieldInfo field) {
S
Skylot 已提交
352
		for (FieldNode f : fields) {
353
			if (f.getFieldInfo().equals(field)) {
S
Skylot 已提交
354
				return f;
S
Skylot 已提交
355
			}
S
Skylot 已提交
356 357 358 359
		}
		return null;
	}

360 361 362 363 364 365 366 367 368
	public FieldNode searchFieldByNameAndType(FieldInfo field) {
		for (FieldNode f : fields) {
			if (f.getFieldInfo().equalsNameAndType(field)) {
				return f;
			}
		}
		return null;
	}

S
Skylot 已提交
369
	public FieldNode searchFieldByName(String name) {
S
Skylot 已提交
370
		for (FieldNode f : fields) {
S
Skylot 已提交
371
			if (f.getName().equals(name)) {
S
Skylot 已提交
372
				return f;
S
Skylot 已提交
373
			}
S
Skylot 已提交
374 375 376 377
		}
		return null;
	}

S
Skylot 已提交
378
	public MethodNode searchMethod(MethodInfo mth) {
S
Skylot 已提交
379
		return mthInfoMap.get(mth);
S
Skylot 已提交
380 381
	}

382
	public MethodNode searchMethodByShortId(String shortId) {
S
Skylot 已提交
383
		for (MethodNode m : methods) {
S
Skylot 已提交
384
			if (m.getMethodInfo().getShortId().equals(shortId)) {
S
Skylot 已提交
385
				return m;
S
Skylot 已提交
386
			}
S
Skylot 已提交
387 388 389 390
		}
		return null;
	}

391 392
	/**
	 * Return first method by original short name
393 394
	 * Note: methods are not unique by name (class can have several methods with same name but different
	 * signature)
395 396 397 398 399 400 401 402 403 404 405
	 */
	@Nullable
	public MethodNode searchMethodByShortName(String name) {
		for (MethodNode m : methods) {
			if (m.getMethodInfo().getName().equals(name)) {
				return m;
			}
		}
		return null;
	}

406 407 408
	public ClassNode getParentClass() {
		if (parentClass == null) {
			if (clsInfo.isInner()) {
409
				ClassNode parent = root.resolveClass(clsInfo.getParentClass());
410
				parentClass = parent == null ? this : parent;
411 412 413 414 415 416 417
			} else {
				parentClass = this;
			}
		}
		return parentClass;
	}

418 419
	public ClassNode getTopParentClass() {
		ClassNode parent = getParentClass();
420
		return parent == this ? this : parent.getTopParentClass();
421 422
	}

423 424 425 426 427 428 429 430 431 432
	public void visitParentClasses(Consumer<ClassNode> consumer) {
		ClassNode currentCls = this;
		ClassNode parentCls = currentCls.getParentClass();
		while (parentCls != currentCls) {
			consumer.accept(parentCls);
			currentCls = parentCls;
			parentCls = currentCls.getParentClass();
		}
	}

433 434 435 436 437 438 439 440 441 442 443
	public boolean hasNotGeneratedParent() {
		if (contains(AFlag.DONT_GENERATE)) {
			return true;
		}
		ClassNode parent = getParentClass();
		if (parent == this) {
			return false;
		}
		return parent.hasNotGeneratedParent();
	}

S
Skylot 已提交
444 445 446 447
	public List<ClassNode> getInnerClasses() {
		return innerClasses;
	}

448
	/**
449
	 * Get all inner and inlined classes recursively
450
	 *
451
	 * @param resultClassesSet all identified inner and inlined classes are added to this set
452
	 */
453 454 455 456 457 458 459 460 461 462
	public void getInnerAndInlinedClassesRecursive(Set<ClassNode> resultClassesSet) {
		for (ClassNode innerCls : innerClasses) {
			if (resultClassesSet.add(innerCls)) {
				innerCls.getInnerAndInlinedClassesRecursive(resultClassesSet);
			}
		}
		for (ClassNode inlinedCls : inlinedClasses) {
			if (resultClassesSet.add(inlinedCls)) {
				inlinedCls.getInnerAndInlinedClassesRecursive(resultClassesSet);
			}
463 464 465
		}
	}

S
Skylot 已提交
466
	public void addInnerClass(ClassNode cls) {
467 468 469
		if (innerClasses.isEmpty()) {
			innerClasses = new ArrayList<>(5);
		}
S
Skylot 已提交
470
		innerClasses.add(cls);
471
		cls.parentClass = this;
S
Skylot 已提交
472 473
	}

474 475 476 477 478 479 480
	public void addInlinedClass(ClassNode cls) {
		if (inlinedClasses.isEmpty()) {
			inlinedClasses = new ArrayList<>(5);
		}
		inlinedClasses.add(cls);
	}

481
	public boolean isEnum() {
S
Skylot 已提交
482 483 484
		return getAccessFlags().isEnum()
				&& getSuperClass() != null
				&& getSuperClass().getObject().equals(ArgType.ENUM.getObject());
485 486
	}

S
Skylot 已提交
487
	public boolean isAnonymous() {
488
		return contains(AFlag.ANONYMOUS_CLASS);
489 490
	}

491 492 493 494
	public boolean isInner() {
		return parentClass != null;
	}

495 496
	@Nullable
	public MethodNode getClassInitMth() {
497
		return searchMethodByShortId("<clinit>()V");
498 499 500
	}

	@Nullable
S
Skylot 已提交
501 502
	public MethodNode getDefaultConstructor() {
		for (MethodNode mth : methods) {
503
			if (mth.isDefaultConstructor()) {
S
Skylot 已提交
504
				return mth;
S
Skylot 已提交
505 506
			}
		}
S
Skylot 已提交
507
		return null;
S
Skylot 已提交
508 509
	}

510
	@Override
S
Skylot 已提交
511 512 513 514
	public AccessInfo getAccessFlags() {
		return accessFlags;
	}

515 516 517 518 519
	@Override
	public void setAccessFlags(AccessInfo accessFlags) {
		this.accessFlags = accessFlags;
	}

520 521
	@Override
	public RootNode root() {
522
		return root;
523 524
	}

525 526 527 528 529
	@Override
	public String typeName() {
		return "class";
	}

S
Skylot 已提交
530 531 532 533 534 535 536
	public String getRawName() {
		return clsInfo.getRawName();
	}

	/**
	 * Internal class info (don't use in code generation and external api).
	 */
S
Skylot 已提交
537 538 539 540 541
	public ClassInfo getClassInfo() {
		return clsInfo;
	}

	public String getShortName() {
542
		return clsInfo.getAliasShortName();
S
Skylot 已提交
543 544 545
	}

	public String getFullName() {
546
		return clsInfo.getAliasFullName();
S
Skylot 已提交
547 548 549
	}

	public String getPackage() {
550
		return clsInfo.getAliasPkg();
551 552
	}

553
	public String getSmali() {
554
		if (smali == null) {
555 556 557
			StringBuilder sb = new StringBuilder();
			getSmali(sb);
			sb.append(System.lineSeparator());
558 559 560
			Set<ClassNode> allInlinedClasses = new LinkedHashSet<>();
			getInnerAndInlinedClassesRecursive(allInlinedClasses);
			for (ClassNode innerClass : allInlinedClasses) {
561 562
				innerClass.getSmali(sb);
				sb.append(System.lineSeparator());
563
			}
564
			smali = sb.toString();
565
		}
566 567 568
		return smali;
	}

569 570 571 572
	protected void getSmali(StringBuilder sb) {
		if (this.clsData == null) {
			sb.append(String.format("###### Class %s is created by jadx", getFullName()));
			return;
573
		}
574 575 576
		sb.append(String.format("###### Class %s (%s)", getFullName(), getRawName()));
		sb.append(System.lineSeparator());
		sb.append(this.clsData.getDisassembledCode());
577 578
	}

579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601
	public String getSmaliV2() {
		StringBuilder sb = new StringBuilder();
		getSmaliV2(sb);
		sb.append(System.lineSeparator());
		Set<ClassNode> allInlinedClasses = new LinkedHashSet<>();
		getInnerAndInlinedClassesRecursive(allInlinedClasses);
		for (ClassNode innerClass : allInlinedClasses) {
			innerClass.getSmaliV2(sb);
			sb.append(System.lineSeparator());
		}
		return sb.toString();
	}

	private void getSmaliV2(StringBuilder sb) {
		if (this.clsData == null) {
			sb.append(String.format("###### Class %s is created by jadx", getFullName()));
			return;
		}
		sb.append(String.format("###### Class %s (%s)", getFullName(), getRawName()));
		sb.append(System.lineSeparator());
		sb.append(this.clsData.getDisassembledCodeV2());
	}

602 603 604 605 606 607 608 609
	public ProcessState getState() {
		return state;
	}

	public void setState(ProcessState state) {
		this.state = state;
	}

610 611 612 613
	public LoadStage getLoadStage() {
		return loadStage;
	}

614 615
	public void setLoadStage(LoadStage loadStage) {
		this.loadStage = loadStage;
616 617 618 619 620
	}

	public void reloadAtCodegenStage() {
		ClassNode topCls = this.getTopParentClass();
		if (topCls.getLoadStage() == LoadStage.CODEGEN_STAGE) {
621
			throw new JadxRuntimeException("Class not yet loaded at codegen stage: " + topCls);
622 623 624 625
		}
		topCls.add(AFlag.RELOAD_AT_CODEGEN_STAGE);
	}

S
Skylot 已提交
626
	public List<ClassNode> getDependencies() {
627 628 629
		return dependencies;
	}

S
Skylot 已提交
630 631 632 633
	public void setDependencies(List<ClassNode> dependencies) {
		this.dependencies = dependencies;
	}

634 635 636 637 638 639 640 641 642 643
	public List<ClassNode> getUseIn() {
		return useIn;
	}

	public void setUseIn(List<ClassNode> useIn) {
		this.useIn = useIn;
	}

	public List<MethodNode> getUseInMth() {
		return useInMth;
644 645
	}

646 647
	public void setUseInMth(List<MethodNode> useInMth) {
		this.useInMth = useInMth;
648 649
	}

650
	@Override
651 652
	public String getInputFileName() {
		return clsData == null ? "synthetic" : clsData.getInputFileName();
653 654
	}

655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671
	@Override
	public int hashCode() {
		return clsInfo.hashCode();
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) {
			return true;
		}
		if (o instanceof ClassNode) {
			ClassNode other = (ClassNode) o;
			return clsInfo.equals(other.clsInfo);
		}
		return false;
	}

672 673 674 675 676
	@Override
	public int compareTo(@NotNull ClassNode o) {
		return this.getFullName().compareTo(o.getFullName());
	}

S
Skylot 已提交
677 678
	@Override
	public String toString() {
S
Skylot 已提交
679
		return clsInfo.getFullName();
S
Skylot 已提交
680 681
	}
}