diff options
| author | 2020-07-19 16:42:17 +0800 | |
|---|---|---|
| committer | 2020-07-19 17:03:26 +0800 | |
| commit | 08ed439aa1deaeefe4145d5c00a0f79e15f62751 (patch) | |
| tree | 8a6c0db8b81d871517fd2ffa22a32dc12c9c9c2c | |
| parent | add MappingValidator based test (diff) | |
| download | enigma-08ed439aa1deaeefe4145d5c00a0f79e15f62751.tar.gz enigma-08ed439aa1deaeefe4145d5c00a0f79e15f62751.tar.xz enigma-08ed439aa1deaeefe4145d5c00a0f79e15f62751.zip | |
allow name clashes to pass where javac would accept them
| -rw-r--r-- | enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java | 116 | ||||
| -rw-r--r-- | enigma/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java | 21 |
2 files changed, 111 insertions, 26 deletions
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java index f9f3b88e..134c05ce 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java | |||
| @@ -1,15 +1,19 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping; | 1 | package cuchaz.enigma.translation.mapping; |
| 2 | 2 | ||
| 3 | import java.util.Collection; | 3 | import java.util.Collection; |
| 4 | import java.util.HashSet; | 4 | import java.util.Objects; |
| 5 | import java.util.Set; | ||
| 5 | import java.util.stream.Collectors; | 6 | import java.util.stream.Collectors; |
| 6 | 7 | ||
| 7 | import cuchaz.enigma.analysis.index.InheritanceIndex; | 8 | import cuchaz.enigma.analysis.index.InheritanceIndex; |
| 8 | import cuchaz.enigma.analysis.index.JarIndex; | 9 | import cuchaz.enigma.analysis.index.JarIndex; |
| 9 | import cuchaz.enigma.translation.Translator; | 10 | import cuchaz.enigma.translation.Translator; |
| 10 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | 11 | import cuchaz.enigma.translation.mapping.tree.EntryTree; |
| 12 | import cuchaz.enigma.translation.representation.AccessFlags; | ||
| 11 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | 13 | import cuchaz.enigma.translation.representation.entry.ClassEntry; |
| 14 | import cuchaz.enigma.translation.representation.entry.DefEntry; | ||
| 12 | import cuchaz.enigma.translation.representation.entry.Entry; | 15 | import cuchaz.enigma.translation.representation.entry.Entry; |
| 16 | import cuchaz.enigma.translation.representation.entry.FieldEntry; | ||
| 13 | import cuchaz.enigma.utils.validation.Message; | 17 | import cuchaz.enigma.utils.validation.Message; |
| 14 | import cuchaz.enigma.utils.validation.ValidationContext; | 18 | import cuchaz.enigma.utils.validation.ValidationContext; |
| 15 | 19 | ||
| @@ -35,18 +39,16 @@ public class MappingValidator { | |||
| 35 | 39 | ||
| 36 | private void validateUnique(ValidationContext vc, Entry<?> entry, String name) { | 40 | private void validateUnique(ValidationContext vc, Entry<?> entry, String name) { |
| 37 | ClassEntry containingClass = entry.getContainingClass(); | 41 | ClassEntry containingClass = entry.getContainingClass(); |
| 38 | Collection<ClassEntry> relatedClasses = getRelatedClasses(containingClass); | 42 | InheritanceIndex inheritanceIndex = index.getInheritanceIndex(); |
| 39 | |||
| 40 | for (ClassEntry relatedClass : relatedClasses) { | ||
| 41 | Entry<?> relatedEntry = entry.replaceAncestor(containingClass, relatedClass); | ||
| 42 | Entry<?> translatedEntry = deobfuscator.translate(relatedEntry); | ||
| 43 | |||
| 44 | Collection<Entry<?>> translatedSiblings = obfToDeobf.getSiblings(relatedEntry).stream() | ||
| 45 | .map(deobfuscator::translate) | ||
| 46 | .collect(Collectors.toList()); | ||
| 47 | 43 | ||
| 48 | if (!isUnique(translatedEntry, translatedSiblings, name)) { | 44 | //Step 1, check it's unique within its own siblings |
| 49 | Entry<?> parent = translatedEntry.getParent(); | 45 | Collection<Entry<?>> directTranslatedSiblings = obfToDeobf.getSiblings(entry).stream() |
| 46 | .map(deobfuscator::translate) | ||
| 47 | .collect(Collectors.toList()); | ||
| 48 | for (Entry<?> sibling : directTranslatedSiblings) { | ||
| 49 | if (entry.canConflictWith(sibling) && sibling.getName().equals(name) && isSynthetic(entry) == isSynthetic(sibling)) { | ||
| 50 | // allow clash if one is synthetic and the other is not | ||
| 51 | Entry<?> parent = entry.getParent(); | ||
| 50 | if (parent != null) { | 52 | if (parent != null) { |
| 51 | vc.raise(Message.NONUNIQUE_NAME_CLASS, name, parent); | 53 | vc.raise(Message.NONUNIQUE_NAME_CLASS, name, parent); |
| 52 | } else { | 54 | } else { |
| @@ -54,26 +56,88 @@ public class MappingValidator { | |||
| 54 | } | 56 | } |
| 55 | } | 57 | } |
| 56 | } | 58 | } |
| 57 | } | ||
| 58 | 59 | ||
| 59 | private Collection<ClassEntry> getRelatedClasses(ClassEntry classEntry) { | 60 | //Step 2, check ancestors, ignoring members invisible to children |
| 60 | InheritanceIndex inheritanceIndex = index.getInheritanceIndex(); | 61 | Set<ClassEntry> ancestors = inheritanceIndex.getAncestors(containingClass); |
| 62 | for (ClassEntry ancestor : ancestors) { | ||
| 63 | Entry<?> reparentedEntry = entry.replaceAncestor(containingClass, ancestor); | ||
| 64 | Entry<?> translatedEntry = Objects.requireNonNull(deobfuscator.translate(reparentedEntry), "Translation failed"); | ||
| 65 | Collection<Entry<?>> translatedSiblings = obfToDeobf.getSiblings(reparentedEntry).stream() | ||
| 66 | .filter(it->!entry.equals(it))//e.g. for root classes, ensure we dont match the name against itself | ||
| 67 | .filter(this::isVisibleToChildren) | ||
| 68 | .collect(Collectors.toList()); | ||
| 69 | for (Entry<?> parentSibling : translatedSiblings) { | ||
| 70 | Entry<?> parentSiblingTranslated = Objects.requireNonNull(deobfuscator.translate(parentSibling), "Translation failed"); | ||
| 71 | if (translatedEntry.canConflictWith(parentSiblingTranslated) && parentSiblingTranslated.getName().equals(name) && !isAcceptableOverride(parentSibling, entry)) { | ||
| 72 | Entry<?> parent = translatedEntry.getParent(); | ||
| 73 | if (parent != null) { | ||
| 74 | vc.raise(Message.NONUNIQUE_NAME_CLASS, name, parent); | ||
| 75 | } else { | ||
| 76 | vc.raise(Message.NONUNIQUE_NAME, name); | ||
| 77 | } | ||
| 78 | } | ||
| 79 | } | ||
| 80 | } | ||
| 61 | 81 | ||
| 62 | Collection<ClassEntry> relatedClasses = new HashSet<>(); | 82 | //Step 3, if this entry is visible to children, see if it clashes with any of their names |
| 63 | relatedClasses.add(classEntry); | 83 | if (isVisibleToChildren(entry)) { |
| 64 | relatedClasses.addAll(inheritanceIndex.getChildren(classEntry)); | 84 | Collection<ClassEntry> children = inheritanceIndex.getDescendants(containingClass); |
| 65 | relatedClasses.addAll(inheritanceIndex.getAncestors(classEntry)); | 85 | for (ClassEntry child : children) { |
| 86 | Entry<?> reparentedEntry = entry.replaceAncestor(containingClass, child); | ||
| 87 | Entry<?> translatedEntry = Objects.requireNonNull(deobfuscator.translate(reparentedEntry), "Translation failed"); | ||
| 88 | Collection<Entry<?>> siblings = obfToDeobf.getSiblings(reparentedEntry).stream() | ||
| 89 | .filter(it->!entry.equals(it))//e.g. for root classes, ensure we dont match the name against itself | ||
| 90 | .collect(Collectors.toList()); | ||
| 91 | for (Entry<?> childSibling : siblings) { | ||
| 92 | Entry<?> childSiblingTranslated = Objects.requireNonNull(deobfuscator.translate(childSibling), "Translation failed"); | ||
| 93 | if (translatedEntry.canConflictWith(childSiblingTranslated) && childSiblingTranslated.getName().equals(name) && !isAcceptableOverride(entry, childSibling)) { | ||
| 94 | Entry<?> parent = translatedEntry.getParent(); | ||
| 95 | if (parent != null) { | ||
| 96 | vc.raise(Message.NONUNIQUE_NAME_CLASS, name, parent); | ||
| 97 | } else { | ||
| 98 | vc.raise(Message.NONUNIQUE_NAME, name); | ||
| 99 | } | ||
| 100 | } | ||
| 101 | } | ||
| 102 | } | ||
| 103 | } | ||
| 104 | } | ||
| 66 | 105 | ||
| 67 | return relatedClasses; | 106 | private boolean isVisibleToChildren(Entry<?> entry) { |
| 107 | if (entry instanceof DefEntry) { | ||
| 108 | return !((DefEntry<?>) entry).getAccess().isPrivate(); | ||
| 109 | } | ||
| 110 | AccessFlags accessFlags = index.getEntryIndex().getEntryAccess(entry); | ||
| 111 | if (accessFlags != null) { | ||
| 112 | return !accessFlags.isPrivate(); | ||
| 113 | } | ||
| 114 | return true;//unknown, assume yes | ||
| 68 | } | 115 | } |
| 69 | 116 | ||
| 70 | private boolean isUnique(Entry<?> entry, Collection<Entry<?>> siblings, String name) { | 117 | private boolean isAcceptableOverride(Entry<?> ancestor, Entry<?> descendent) { |
| 71 | for (Entry<?> sibling : siblings) { | 118 | if (ancestor instanceof FieldEntry && descendent instanceof FieldEntry){ |
| 72 | if (entry.canConflictWith(sibling) && sibling.getName().equals(name)) { | 119 | return true;//fields don't apply here |
| 73 | return false; | 120 | } |
| 74 | } | 121 | |
| 122 | AccessFlags ancestorFlags = findAccessFlags(ancestor); | ||
| 123 | AccessFlags descendentFlags = findAccessFlags(descendent); | ||
| 124 | |||
| 125 | if (ancestorFlags == null || descendentFlags == null) { | ||
| 126 | return false;//we can't make any assumptions | ||
| 75 | } | 127 | } |
| 76 | return true; | 128 | |
| 129 | //bad == accessLevel < superAccessLevel | ||
| 130 | return !(descendentFlags.getAccessLevel() < ancestorFlags.getAccessLevel()); | ||
| 131 | } | ||
| 132 | |||
| 133 | private boolean isSynthetic(Entry<?> entry) { | ||
| 134 | AccessFlags accessFlags = findAccessFlags(entry); | ||
| 135 | return accessFlags != null && accessFlags.isSynthetic(); | ||
| 136 | } | ||
| 137 | |||
| 138 | private AccessFlags findAccessFlags(Entry<?> entry) { | ||
| 139 | return (entry instanceof DefEntry) ? ((DefEntry<?>) entry).getAccess() : index.getEntryIndex() | ||
| 140 | .getEntryAccess(entry); | ||
| 77 | } | 141 | } |
| 78 | 142 | ||
| 79 | } | 143 | } |
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java index b280eef2..aa48a5b2 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java | |||
| @@ -8,6 +8,10 @@ import java.lang.reflect.Modifier; | |||
| 8 | public class AccessFlags { | 8 | public class AccessFlags { |
| 9 | public static final AccessFlags PRIVATE = new AccessFlags(Opcodes.ACC_PRIVATE); | 9 | public static final AccessFlags PRIVATE = new AccessFlags(Opcodes.ACC_PRIVATE); |
| 10 | public static final AccessFlags PUBLIC = new AccessFlags(Opcodes.ACC_PUBLIC); | 10 | public static final AccessFlags PUBLIC = new AccessFlags(Opcodes.ACC_PUBLIC); |
| 11 | public static final int ACCESS_LEVEL_PUBLIC = 4; | ||
| 12 | public static final int ACCESS_LEVEL_PROTECTED = 3; | ||
| 13 | public static final int ACCESS_LEVEL_PACKAGE_LOCAL = 2; | ||
| 14 | public static final int ACCESS_LEVEL_PRIVATE = 1; | ||
| 11 | 15 | ||
| 12 | private int flags; | 16 | private int flags; |
| 13 | 17 | ||
| @@ -89,6 +93,23 @@ public class AccessFlags { | |||
| 89 | return this.flags; | 93 | return this.flags; |
| 90 | } | 94 | } |
| 91 | 95 | ||
| 96 | /** | ||
| 97 | * Adapted from https://github.com/JetBrains/intellij-community/blob/6472c347db91d11bbf02895a767198f9d884b119/java/java-psi-api/src/com/intellij/psi/util/PsiUtil.java#L389 | ||
| 98 | * @return visibility access level on a 'weakness scale' | ||
| 99 | */ | ||
| 100 | public int getAccessLevel() { | ||
| 101 | if (isPrivate()) { | ||
| 102 | return ACCESS_LEVEL_PRIVATE; | ||
| 103 | } | ||
| 104 | if (isProtected()) { | ||
| 105 | return ACCESS_LEVEL_PROTECTED; | ||
| 106 | } | ||
| 107 | if (isPublic()) { | ||
| 108 | return ACCESS_LEVEL_PUBLIC; | ||
| 109 | } | ||
| 110 | return ACCESS_LEVEL_PACKAGE_LOCAL; | ||
| 111 | } | ||
| 112 | |||
| 92 | @Override | 113 | @Override |
| 93 | public boolean equals(Object obj) { | 114 | public boolean equals(Object obj) { |
| 94 | return obj instanceof AccessFlags && ((AccessFlags) obj).flags == flags; | 115 | return obj instanceof AccessFlags && ((AccessFlags) obj).flags == flags; |