|
15 | 15 | import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry; |
16 | 16 | import org.quiltmc.enigma.api.translation.representation.entry.FieldEntry; |
17 | 17 | import org.quiltmc.enigma.api.translation.representation.entry.MethodEntry; |
| 18 | +import org.quiltmc.enigma.impl.plugin.RecordComponentProposalService; |
18 | 19 |
|
19 | 20 | import java.io.IOException; |
20 | 21 | import java.io.Reader; |
21 | 22 | import java.io.StringReader; |
22 | 23 | import java.nio.file.Path; |
23 | 24 |
|
| 25 | +/** |
| 26 | + * Many record tests rely on the fact that proguard consistently names things in order a, b, c... which results in |
| 27 | + * most default record component getters having the same name as their fields.<br> |
| 28 | + * Changing proguard's naming configs could break many tests. |
| 29 | + */ |
24 | 30 | public class TestRecordComponentProposal { |
25 | 31 | private static final Path JAR = TestUtil.obfJar("records"); |
26 | 32 | private static EnigmaProject project; |
@@ -71,32 +77,129 @@ void testSimpleRecordComponentProposal() { |
71 | 77 | } |
72 | 78 |
|
73 | 79 | @Test |
74 | | - void testMismatchRecordComponentProposal() { |
75 | | - // name of getter mismatches with name of field |
76 | | - ClassEntry cClass = TestEntryFactory.newClass("d"); |
77 | | - FieldEntry aField = TestEntryFactory.newField(cClass, "a", "I"); |
78 | | - MethodEntry fakeAGetter = TestEntryFactory.newMethod(cClass, "a", "()I"); |
79 | | - MethodEntry realAGetter = TestEntryFactory.newMethod(cClass, "b", "()I"); |
| 80 | + void testFakeGetterWrongInstructions() { |
| 81 | + final ClassEntry fakeGetterWrongInstructionsRecord = TestEntryFactory.newClass("h"); |
| 82 | + final FieldEntry componentField = TestEntryFactory.newField(fakeGetterWrongInstructionsRecord, "a", "I"); |
| 83 | + final MethodEntry fakeGetter = TestEntryFactory.newMethod(fakeGetterWrongInstructionsRecord, "a", "()I"); |
| 84 | + final MethodEntry componentGetter = TestEntryFactory.newMethod(fakeGetterWrongInstructionsRecord, "b", "()I"); |
80 | 85 |
|
81 | | - Assertions.assertSame(TokenType.OBFUSCATED, project.getRemapper().getMapping(aField).tokenType()); |
82 | | - Assertions.assertSame(TokenType.OBFUSCATED, project.getRemapper().getMapping(fakeAGetter).tokenType()); |
83 | | - Assertions.assertSame(TokenType.OBFUSCATED, project.getRemapper().getMapping(realAGetter).tokenType()); |
| 86 | + Assertions.assertSame(TokenType.OBFUSCATED, project.getRemapper().getMapping(componentField).tokenType()); |
| 87 | + Assertions.assertSame(TokenType.OBFUSCATED, project.getRemapper().getMapping(fakeGetter).tokenType()); |
| 88 | + Assertions.assertSame(TokenType.OBFUSCATED, project.getRemapper().getMapping(componentGetter).tokenType()); |
84 | 89 |
|
85 | | - project.getRemapper().putMapping(TestUtil.newVC(), aField, new EntryMapping("mapped")); |
| 90 | + final String targetName = "mapped"; |
| 91 | + project.getRemapper().putMapping(TestUtil.newVC(), componentField, new EntryMapping(targetName)); |
86 | 92 |
|
87 | | - var fieldMapping = project.getRemapper().getMapping(aField); |
88 | | - Assertions.assertEquals(TokenType.DEOBFUSCATED, fieldMapping.tokenType()); |
89 | | - Assertions.assertEquals("mapped", fieldMapping.targetName()); |
| 93 | + final EntryMapping fieldMapping = project.getRemapper().getMapping(componentField); |
| 94 | + Assertions.assertSame(TokenType.DEOBFUSCATED, fieldMapping.tokenType()); |
| 95 | + Assertions.assertEquals(targetName, fieldMapping.targetName()); |
90 | 96 |
|
91 | 97 | // fake getter should NOT be mapped |
92 | | - var fakeGetterMapping = project.getRemapper().getMapping(fakeAGetter); |
| 98 | + final EntryMapping fakeGetterMapping = project.getRemapper().getMapping(fakeGetter); |
93 | 99 | Assertions.assertEquals(TokenType.OBFUSCATED, fakeGetterMapping.tokenType()); |
94 | 100 |
|
95 | | - // real getter SHOULD be mapped |
96 | | - var realGetterMapping = project.getRemapper().getMapping(realAGetter); |
97 | | - Assertions.assertEquals(TokenType.DYNAMIC_PROPOSED, realGetterMapping.tokenType()); |
98 | | - Assertions.assertEquals("mapped", realGetterMapping.targetName()); |
99 | | - Assertions.assertEquals("enigma:record_component_proposer", realGetterMapping.sourcePluginId()); |
| 101 | + // real getter should also NOT be mapped |
| 102 | + // it's impossible to determine that it's the real getter |
| 103 | + // this behavior matches decompilers' |
| 104 | + final EntryMapping componentGetterMapping = project.getRemapper().getMapping(componentGetter); |
| 105 | + Assertions.assertEquals(TokenType.OBFUSCATED, componentGetterMapping.tokenType()); |
| 106 | + } |
| 107 | + |
| 108 | + @Test |
| 109 | + void testFakeGetterRightInstructions() { |
| 110 | + final ClassEntry fakeGetterRightInstructionsRecord = TestEntryFactory.newClass("g"); |
| 111 | + final FieldEntry componentField = TestEntryFactory.newField(fakeGetterRightInstructionsRecord, "a", "I"); |
| 112 | + final MethodEntry fakeGetter = TestEntryFactory.newMethod(fakeGetterRightInstructionsRecord, "a", "()I"); |
| 113 | + final MethodEntry componentGetter = TestEntryFactory.newMethod(fakeGetterRightInstructionsRecord, "b", "()I"); |
| 114 | + |
| 115 | + Assertions.assertSame(TokenType.OBFUSCATED, project.getRemapper().getMapping(componentField).tokenType()); |
| 116 | + Assertions.assertSame(TokenType.OBFUSCATED, project.getRemapper().getMapping(fakeGetter).tokenType()); |
| 117 | + Assertions.assertSame(TokenType.OBFUSCATED, project.getRemapper().getMapping(componentGetter).tokenType()); |
| 118 | + |
| 119 | + final String targetName = "mapped"; |
| 120 | + project.getRemapper().putMapping(TestUtil.newVC(), componentField, new EntryMapping(targetName)); |
| 121 | + |
| 122 | + // FAKE getter SHOULD be mapped |
| 123 | + // Assuming it's the getter - based on name, access, descriptor and instructions - matches decompilers' |
| 124 | + // assumptions. |
| 125 | + // Decompilers assume it's a default getter and hide it, so we propose a name to prevent un-completable stats. |
| 126 | + final EntryMapping fakeGetterMappings = project.getRemapper().getMapping(fakeGetter); |
| 127 | + Assertions.assertEquals(TokenType.DYNAMIC_PROPOSED, fakeGetterMappings.tokenType()); |
| 128 | + Assertions.assertEquals(targetName, fakeGetterMappings.targetName()); |
| 129 | + Assertions.assertEquals(RecordComponentProposalService.ID, fakeGetterMappings.sourcePluginId()); |
| 130 | + |
| 131 | + // real getter should NOT be mapped |
| 132 | + final EntryMapping componentGetterMapping = project.getRemapper().getMapping(componentGetter); |
| 133 | + Assertions.assertEquals(TokenType.OBFUSCATED, componentGetterMapping.tokenType()); |
| 134 | + } |
| 135 | + |
| 136 | + @Test |
| 137 | + void testBridgeRecord() { |
| 138 | + final String doubleDesc = "Ljava/lang/Double;"; |
| 139 | + final String stringGetterDesc = "()" + doubleDesc; |
| 140 | + |
| 141 | + final ClassEntry bridgeRecord = TestEntryFactory.newClass("f"); |
| 142 | + final FieldEntry getField = TestEntryFactory.newField(bridgeRecord, "a", doubleDesc); |
| 143 | + final MethodEntry getGetter = TestEntryFactory.newMethod(bridgeRecord, "a", stringGetterDesc); |
| 144 | + final MethodEntry getBridge = TestEntryFactory.newMethod(bridgeRecord, "get", "()Ljava/lang/Object;"); |
| 145 | + |
| 146 | + Assertions.assertSame(TokenType.OBFUSCATED, project.getRemapper().getMapping(getField).tokenType()); |
| 147 | + Assertions.assertSame(TokenType.OBFUSCATED, project.getRemapper().getMapping(getGetter).tokenType()); |
| 148 | + Assertions.assertSame(TokenType.OBFUSCATED, project.getRemapper().getMapping(getBridge).tokenType()); |
| 149 | + |
| 150 | + final String targetName = "mapped"; |
| 151 | + project.getRemapper().putMapping(TestUtil.newVC(), getField, new EntryMapping(targetName)); |
| 152 | + |
| 153 | + final EntryMapping fieldMapping = project.getRemapper().getMapping(getField); |
| 154 | + Assertions.assertSame(TokenType.DEOBFUSCATED, fieldMapping.tokenType()); |
| 155 | + Assertions.assertEquals(targetName, fieldMapping.targetName()); |
| 156 | + |
| 157 | + // getter should be mapped; it should be the only getter candidate |
| 158 | + final EntryMapping getterMapping = project.getRemapper().getMapping(getGetter); |
| 159 | + Assertions.assertSame(TokenType.DYNAMIC_PROPOSED, getterMapping.tokenType()); |
| 160 | + Assertions.assertEquals(targetName, getterMapping.targetName()); |
| 161 | + Assertions.assertEquals(RecordComponentProposalService.ID, getterMapping.sourcePluginId()); |
| 162 | + |
| 163 | + // bridge should not be mapped; it should not be a getter candidate because |
| 164 | + // it has the wrong access and descriptor |
| 165 | + final EntryMapping bridgeMapping = project.getRemapper().getMapping(getBridge); |
| 166 | + Assertions.assertEquals(TokenType.OBFUSCATED, bridgeMapping.tokenType()); |
| 167 | + } |
| 168 | + |
| 169 | + @Test |
| 170 | + void testIllegalGetterNameExclusion() { |
| 171 | + final String stringDesc = "Ljava/lang/String;"; |
| 172 | + final String stringGetterDesc = "()" + stringDesc; |
| 173 | + |
| 174 | + final ClassEntry stringComponentOverrideGetterRecord = TestEntryFactory.newClass("i"); |
| 175 | + final FieldEntry stringField = TestEntryFactory.newField(stringComponentOverrideGetterRecord, "a", stringDesc); |
| 176 | + final MethodEntry stringGetter = TestEntryFactory |
| 177 | + .newMethod(stringComponentOverrideGetterRecord, "a", stringGetterDesc); |
| 178 | + final MethodEntry toString = TestEntryFactory |
| 179 | + .newMethod(stringComponentOverrideGetterRecord, "toString", stringGetterDesc); |
| 180 | + |
| 181 | + Assertions.assertSame(TokenType.OBFUSCATED, project.getRemapper().getMapping(stringField).tokenType()); |
| 182 | + Assertions.assertSame(TokenType.OBFUSCATED, project.getRemapper().getMapping(stringGetter).tokenType()); |
| 183 | + Assertions.assertSame(TokenType.OBFUSCATED, project.getRemapper().getMapping(toString).tokenType()); |
| 184 | + |
| 185 | + final String targetName = "mapped"; |
| 186 | + project.getRemapper().putMapping(TestUtil.newVC(), stringField, new EntryMapping(targetName)); |
| 187 | + |
| 188 | + final EntryMapping fieldMapping = project.getRemapper().getMapping(stringField); |
| 189 | + Assertions.assertSame(TokenType.DEOBFUSCATED, fieldMapping.tokenType()); |
| 190 | + Assertions.assertEquals(targetName, fieldMapping.targetName()); |
| 191 | + |
| 192 | + // getter should be mapped; it should be the only getter candidate: toString should be excluded from candidates |
| 193 | + // because its name is not a legal component name |
| 194 | + final EntryMapping getterMapping = project.getRemapper().getMapping(stringGetter); |
| 195 | + Assertions.assertSame(TokenType.DYNAMIC_PROPOSED, getterMapping.tokenType()); |
| 196 | + Assertions.assertEquals(targetName, getterMapping.targetName()); |
| 197 | + Assertions.assertEquals(RecordComponentProposalService.ID, getterMapping.sourcePluginId()); |
| 198 | + |
| 199 | + // toString should not be mapped because it's name doesn't match the field, |
| 200 | + // its name is no a legal component name, and it's a library method (unmappable) |
| 201 | + final EntryMapping bridgeMapping = project.getRemapper().getMapping(toString); |
| 202 | + Assertions.assertEquals(TokenType.OBFUSCATED, bridgeMapping.tokenType()); |
100 | 203 | } |
101 | 204 |
|
102 | 205 | @Test |
|
0 commit comments