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.java52
-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, 258 insertions, 27 deletions
diff --git a/enigma/build.gradle b/enigma/build.gradle
index 7cebcd16..8d081457 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") 56 '-outjars', file('build/test-obf/' + "${theFile.name}.jar"), '-printmapping', file("build/test-obf/${theFile.name}-mapping.txt")
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 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 @@
1package cuchaz.enigma.translation.mapping; 1package cuchaz.enigma.translation.mapping;
2 2
3import java.util.Collection; 3import java.util.Collection;
4import java.util.HashSet; 4import java.util.Objects;
5import java.util.Set;
5import java.util.stream.Collectors; 6import java.util.stream.Collectors;
6 7
7import cuchaz.enigma.analysis.index.InheritanceIndex; 8import cuchaz.enigma.analysis.index.InheritanceIndex;
8import cuchaz.enigma.analysis.index.JarIndex; 9import cuchaz.enigma.analysis.index.JarIndex;
9import cuchaz.enigma.translation.Translator; 10import cuchaz.enigma.translation.Translator;
10import cuchaz.enigma.translation.mapping.tree.EntryTree; 11import cuchaz.enigma.translation.mapping.tree.EntryTree;
12import cuchaz.enigma.translation.representation.AccessFlags;
11import cuchaz.enigma.translation.representation.entry.ClassEntry; 13import cuchaz.enigma.translation.representation.entry.ClassEntry;
14import cuchaz.enigma.translation.representation.entry.DefEntry;
12import cuchaz.enigma.translation.representation.entry.Entry; 15import cuchaz.enigma.translation.representation.entry.Entry;
16import cuchaz.enigma.translation.representation.entry.FieldEntry;
13import cuchaz.enigma.utils.validation.Message; 17import cuchaz.enigma.utils.validation.Message;
14import cuchaz.enigma.utils.validation.ValidationContext; 18import 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;
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;
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;
diff --git a/enigma/src/test/java/cuchaz/enigma/TestAllowableClashes.java b/enigma/src/test/java/cuchaz/enigma/TestAllowableClashes.java
new file mode 100644
index 00000000..bfc21de3
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/TestAllowableClashes.java
@@ -0,0 +1,52 @@
1package cuchaz.enigma;
2
3import cuchaz.enigma.classprovider.ClasspathClassProvider;
4import cuchaz.enigma.translation.mapping.EntryMapping;
5import cuchaz.enigma.translation.mapping.EntryRemapper;
6import cuchaz.enigma.translation.mapping.serde.MappingFormat;
7import cuchaz.enigma.translation.mapping.serde.MappingParseException;
8import cuchaz.enigma.translation.mapping.tree.EntryTree;
9import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
10import cuchaz.enigma.translation.representation.entry.MethodEntry;
11import cuchaz.enigma.utils.validation.ValidationContext;
12import org.hamcrest.MatcherAssert;
13import org.junit.Assert;
14import org.junit.Test;
15
16import java.io.IOException;
17import java.nio.file.Paths;
18
19/**
20 * Test that we can accept some name clashes that are allowed by javac
21 */
22public class TestAllowableClashes {
23
24 private static final String inputBaseName = "build/test-obf/visibility";
25
26 @Test
27 public void test() throws IOException, MappingParseException {
28 //Load produced mappings
29 Enigma enigma = Enigma.create();
30 EnigmaProject project = enigma.openJar(Paths.get(inputBaseName + ".jar"), new ClasspathClassProvider(), ProgressListener.none());
31 EntryTree<EntryMapping> obfToDeobf = MappingFormat.PROGUARD.read(Paths.get(inputBaseName + "-mapping.txt"), ProgressListener.none(), null);
32
33 //Load them into enigma, none should conflict
34 EntryRemapper mapper = project.getMapper();
35 for (int round=0; round<2; round++) {
36 for (EntryTreeNode<EntryMapping> node : obfToDeobf) {
37 Assert.assertNotEquals(null, node.getValue());
38 if (node.getEntry() instanceof MethodEntry && (node.getEntry()
39 .getName()
40 .equals("<init>") || node.getEntry().getName().equals("<clinit>"))) {
41 //skip proguard's constructor entries
42 continue;
43 }
44 System.out.println(node.getEntry().toString() + " -> " + node.getValue().getTargetName());
45 ValidationContext vc = new ValidationContext();
46 mapper.mapFromObf(vc, node.getEntry(), node.getValue());
47 MatcherAssert.assertThat(vc, ValidationContextMatcher.INSTANCE);
48 }
49 }
50 }
51
52}
diff --git a/enigma/src/test/java/cuchaz/enigma/ValidationContextMatcher.java b/enigma/src/test/java/cuchaz/enigma/ValidationContextMatcher.java
new file mode 100644
index 00000000..5404077f
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/ValidationContextMatcher.java
@@ -0,0 +1,35 @@
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
new file mode 100644
index 00000000..458f7c0d
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/visibility/ClassA.java
@@ -0,0 +1,28 @@
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
new file mode 100644
index 00000000..2a1b68a7
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/visibility/ClassB.java
@@ -0,0 +1,31 @@
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}