summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--enigma/build.gradle2
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java116
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java21
-rw-r--r--enigma/src/test/java/cuchaz/enigma/TestAllowableClashes.java51
-rw-r--r--enigma/src/test/java/cuchaz/enigma/ValidationContextMatcher.java35
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/visibility/ClassA.java28
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/visibility/ClassB.java31
7 files changed, 27 insertions, 257 deletions
diff --git a/enigma/build.gradle b/enigma/build.gradle
index 2c5d329f..7cebcd16 100644
--- a/enigma/build.gradle
+++ b/enigma/build.gradle
@@ -53,7 +53,7 @@ file('src/test/java/cuchaz/enigma/inputs').listFiles().each { theFile ->
53 53
54 args '@src/test/resources/proguard-test.conf', '-injars', file('build/test-inputs/' + 54 args '@src/test/resources/proguard-test.conf', '-injars', file('build/test-inputs/' +
55 "${theFile.name}.jar"), '-libraryjars', libraryJarsArg, 55 "${theFile.name}.jar"), '-libraryjars', libraryJarsArg,
56 '-outjars', file('build/test-obf/' + "${theFile.name}.jar"), '-printmapping', file("build/${theFile.name}-mapping.txt") 56 '-outjars', file('build/test-obf/' + "${theFile.name}.jar")
57 } 57 }
58 58
59 test.dependsOn "${theFile.name}TestObf" 59 test.dependsOn "${theFile.name}TestObf"
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 a84b0fb3..f9f3b88e 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java
@@ -1,19 +1,15 @@
1package cuchaz.enigma.translation.mapping; 1package cuchaz.enigma.translation.mapping;
2 2
3import java.util.Collection; 3import java.util.Collection;
4import java.util.Objects; 4import java.util.HashSet;
5import java.util.Set;
6import java.util.stream.Collectors; 5import java.util.stream.Collectors;
7 6
8import cuchaz.enigma.analysis.index.InheritanceIndex; 7import cuchaz.enigma.analysis.index.InheritanceIndex;
9import cuchaz.enigma.analysis.index.JarIndex; 8import cuchaz.enigma.analysis.index.JarIndex;
10import cuchaz.enigma.translation.Translator; 9import cuchaz.enigma.translation.Translator;
11import cuchaz.enigma.translation.mapping.tree.EntryTree; 10import cuchaz.enigma.translation.mapping.tree.EntryTree;
12import cuchaz.enigma.translation.representation.AccessFlags;
13import cuchaz.enigma.translation.representation.entry.ClassEntry; 11import cuchaz.enigma.translation.representation.entry.ClassEntry;
14import cuchaz.enigma.translation.representation.entry.DefEntry;
15import cuchaz.enigma.translation.representation.entry.Entry; 12import cuchaz.enigma.translation.representation.entry.Entry;
16import cuchaz.enigma.translation.representation.entry.FieldEntry;
17import cuchaz.enigma.utils.validation.Message; 13import cuchaz.enigma.utils.validation.Message;
18import cuchaz.enigma.utils.validation.ValidationContext; 14import cuchaz.enigma.utils.validation.ValidationContext;
19 15
@@ -39,16 +35,18 @@ public class MappingValidator {
39 35
40 private void validateUnique(ValidationContext vc, Entry<?> entry, String name) { 36 private void validateUnique(ValidationContext vc, Entry<?> entry, String name) {
41 ClassEntry containingClass = entry.getContainingClass(); 37 ClassEntry containingClass = entry.getContainingClass();
42 InheritanceIndex inheritanceIndex = index.getInheritanceIndex(); 38 Collection<ClassEntry> relatedClasses = getRelatedClasses(containingClass);
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());
43 47
44 //Step 1, check it's unique within its own siblings 48 if (!isUnique(translatedEntry, translatedSiblings, name)) {
45 Collection<Entry<?>> directTranslatedSiblings = obfToDeobf.getSiblings(entry).stream() 49 Entry<?> parent = translatedEntry.getParent();
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();
52 if (parent != null) { 50 if (parent != null) {
53 vc.raise(Message.NONUNIQUE_NAME_CLASS, name, parent); 51 vc.raise(Message.NONUNIQUE_NAME_CLASS, name, parent);
54 } else { 52 } else {
@@ -56,88 +54,26 @@ public class MappingValidator {
56 } 54 }
57 } 55 }
58 } 56 }
59
60 //Step 2, check ancestors, ignoring members invisible to children
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 }
81
82 //Step 3, if this entry is visible to children, see if it clashes with any of their names
83 if (isVisibleToChildren(entry)) {
84 Collection<ClassEntry> children = inheritanceIndex.getDescendants(containingClass);
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 } 57 }
105 58
106 private boolean isVisibleToChildren(Entry<?> entry) { 59 private Collection<ClassEntry> getRelatedClasses(ClassEntry classEntry) {
107 if (entry instanceof DefEntry) { 60 InheritanceIndex inheritanceIndex = index.getInheritanceIndex();
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
115 }
116
117 private boolean isAcceptableOverride(Entry<?> ancestor, Entry<?> descendent) {
118 if (ancestor instanceof FieldEntry && descendent instanceof FieldEntry){
119 return true;//fields don't apply here
120 }
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
127 }
128 61
129 //bad == accessLevel < superAccessLevel 62 Collection<ClassEntry> relatedClasses = new HashSet<>();
130 return !(descendentFlags.getAccessLevel() < ancestorFlags.getAccessLevel()); 63 relatedClasses.add(classEntry);
131 } 64 relatedClasses.addAll(inheritanceIndex.getChildren(classEntry));
65 relatedClasses.addAll(inheritanceIndex.getAncestors(classEntry));
132 66
133 private boolean isSynthetic(Entry<?> entry) { 67 return relatedClasses;
134 AccessFlags accessFlags = findAccessFlags(entry);
135 return accessFlags != null && accessFlags.isSynthetic();
136 } 68 }
137 69
138 private AccessFlags findAccessFlags(Entry<?> entry) { 70 private boolean isUnique(Entry<?> entry, Collection<Entry<?>> siblings, String name) {
139 return (entry instanceof DefEntry) ? ((DefEntry<?>) entry).getAccess() : index.getEntryIndex() 71 for (Entry<?> sibling : siblings) {
140 .getEntryAccess(entry); 72 if (entry.canConflictWith(sibling) && sibling.getName().equals(name)) {
73 return false;
74 }
75 }
76 return true;
141 } 77 }
142 78
143} 79}
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 aa48a5b2..b280eef2 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java
@@ -8,10 +8,6 @@ import java.lang.reflect.Modifier;
8public class AccessFlags { 8public 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;
15 11
16 private int flags; 12 private int flags;
17 13
@@ -93,23 +89,6 @@ public class AccessFlags {
93 return this.flags; 89 return this.flags;
94 } 90 }
95 91
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
113 @Override 92 @Override
114 public boolean equals(Object obj) { 93 public boolean equals(Object obj) {
115 return obj instanceof AccessFlags && ((AccessFlags) obj).flags == flags; 94 return obj instanceof AccessFlags && ((AccessFlags) obj).flags == flags;
diff --git a/enigma/src/test/java/cuchaz/enigma/TestAllowableClashes.java b/enigma/src/test/java/cuchaz/enigma/TestAllowableClashes.java
deleted file mode 100644
index ce82e380..00000000
--- a/enigma/src/test/java/cuchaz/enigma/TestAllowableClashes.java
+++ /dev/null
@@ -1,51 +0,0 @@
1package cuchaz.enigma;
2
3import java.io.IOException;
4import java.nio.file.Paths;
5
6import org.hamcrest.MatcherAssert;
7import org.junit.Assert;
8import org.junit.Test;
9
10import cuchaz.enigma.classprovider.ClasspathClassProvider;
11import cuchaz.enigma.translation.mapping.EntryMapping;
12import cuchaz.enigma.translation.mapping.EntryRemapper;
13import cuchaz.enigma.translation.mapping.serde.MappingFormat;
14import cuchaz.enigma.translation.mapping.serde.MappingParseException;
15import cuchaz.enigma.translation.mapping.tree.EntryTree;
16import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
17import cuchaz.enigma.translation.representation.entry.MethodEntry;
18import cuchaz.enigma.utils.validation.ValidationContext;
19
20/**
21 * Test that we can accept some name clashes that are allowed by javac
22 */
23public class TestAllowableClashes {
24
25 @Test
26 public void test() throws IOException, MappingParseException {
27 //Load produced mappings
28 Enigma enigma = Enigma.create();
29 EnigmaProject project = enigma.openJar(Paths.get("build/test-obf/visibility.jar"), new ClasspathClassProvider(), ProgressListener.none());
30 EntryTree<EntryMapping> obfToDeobf = MappingFormat.PROGUARD.read(Paths.get("build/visibility-mapping.txt"), ProgressListener.none(), null);
31
32 //Load them into enigma, none should conflict
33 EntryRemapper mapper = project.getMapper();
34 for (int round=0; round<2; round++) {
35 for (EntryTreeNode<EntryMapping> node : obfToDeobf) {
36 Assert.assertNotEquals(null, node.getValue());
37 if (node.getEntry() instanceof MethodEntry && (node.getEntry()
38 .getName()
39 .equals("<init>") || node.getEntry().getName().equals("<clinit>"))) {
40 //skip proguard's constructor entries
41 continue;
42 }
43 System.out.println(node.getEntry().toString() + " -> " + node.getValue().getTargetName());
44 ValidationContext vc = new ValidationContext();
45 mapper.mapFromObf(vc, node.getEntry(), node.getValue());
46 MatcherAssert.assertThat(vc, ValidationContextMatcher.INSTANCE);
47 }
48 }
49 }
50
51}
diff --git a/enigma/src/test/java/cuchaz/enigma/ValidationContextMatcher.java b/enigma/src/test/java/cuchaz/enigma/ValidationContextMatcher.java
deleted file mode 100644
index 5404077f..00000000
--- a/enigma/src/test/java/cuchaz/enigma/ValidationContextMatcher.java
+++ /dev/null
@@ -1,35 +0,0 @@
1package cuchaz.enigma;
2
3import cuchaz.enigma.utils.validation.ParameterizedMessage;
4import cuchaz.enigma.utils.validation.ValidationContext;
5import org.hamcrest.CustomMatcher;
6import org.hamcrest.Description;
7
8class ValidationContextMatcher extends CustomMatcher<ValidationContext> {
9 public static final ValidationContextMatcher INSTANCE = new ValidationContextMatcher();
10
11 private ValidationContextMatcher() {
12 super("ValidationContext can proceed");
13 }
14
15 @Override
16 public boolean matches(Object item) {
17 return item instanceof ValidationContext && ((ValidationContext) item).canProceed();
18 }
19
20 @Override
21 public void describeMismatch(Object item, Description description) {
22 if (!(item instanceof ValidationContext)) {
23 description.appendText("expected ValidationContext, was").appendValue(item);
24 return;
25 }
26 ValidationContext vc = (ValidationContext) item;
27 for (ParameterizedMessage message : vc.getMessages()) {
28 description.appendText(message.getText());
29 String longMessage = message.getLongText();
30 if (longMessage != null && !longMessage.trim().isEmpty()){
31 description.appendText(longMessage);
32 }
33 }
34 }
35}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/visibility/ClassA.java b/enigma/src/test/java/cuchaz/enigma/inputs/visibility/ClassA.java
deleted file mode 100644
index 458f7c0d..00000000
--- a/enigma/src/test/java/cuchaz/enigma/inputs/visibility/ClassA.java
+++ /dev/null
@@ -1,28 +0,0 @@
1package cuchaz.enigma.inputs.visibility;
2
3class ClassA {
4
5 protected Object protectedParentPrivateChild;
6 public Object publicParentPrivateChild;
7
8 public static Object LOGGER = null;
9
10 protected static Object LOGGER2 = null;
11
12 public Object publicPublic;
13
14 public static void equalAccessStatic() {}
15
16 protected static void protectedPublicStatic(){}
17
18 private static void privateStaticParentPublicStaticChild(){}
19
20 private void privateParentPublicStaticChild() {}
21
22 static void packagePrivateParentProtectedChild(){}
23
24 private static void packagePrivateChild(){}
25
26 public ClassA returningSubclass(){return null;}
27
28} \ No newline at end of file
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/visibility/ClassB.java b/enigma/src/test/java/cuchaz/enigma/inputs/visibility/ClassB.java
deleted file mode 100644
index 2a1b68a7..00000000
--- a/enigma/src/test/java/cuchaz/enigma/inputs/visibility/ClassB.java
+++ /dev/null
@@ -1,31 +0,0 @@
1package cuchaz.enigma.inputs.visibility;
2
3public class ClassB extends ClassA {
4 private Object protectedParentPrivateChild;
5
6 private Object publicParentPrivateChild;
7
8 public Object publicPublic;
9
10 public static Object LOGGER;
11
12 public static Object LOGGER2 = null;
13
14 public static void equalAccessStatic() {
15 }
16
17 public static void protectedPublicStatic() {
18 }
19
20 public static void privateStaticParentPublicStaticChild() {
21 }
22
23 public static void privateParentPublicStaticChild() {
24 }
25
26 protected static void packagePrivateParentProtectedChild(){}
27
28 static void packagePrivateChild(){}
29
30 public ClassB returningSubclass(){return null;}
31}