From 31958aacd3b57b79b54270ada4408350f6ebbef4 Mon Sep 17 00:00:00 2001 From: Space Walker <spacedoesrs@gmail.com> Date: Wed, 11 Sep 2024 18:49:10 +0200 Subject: [PATCH 1/3] only complete official namespaces for unobfuscated members --- .../mappings/tiny/MappingsMerger.java | 5 +- .../tiny/UnobfuscatedMappingNsCompleter.java | 231 ++++++++++++++++++ 2 files changed, 233 insertions(+), 3 deletions(-) create mode 100644 src/main/java/net/fabricmc/loom/configuration/providers/mappings/tiny/UnobfuscatedMappingNsCompleter.java diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/tiny/MappingsMerger.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/tiny/MappingsMerger.java index e2f6ba67d..07207e620 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/tiny/MappingsMerger.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/tiny/MappingsMerger.java @@ -40,7 +40,6 @@ import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.configuration.providers.mappings.IntermediateMappingsService; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; -import net.fabricmc.mappingio.adapter.MappingNsCompleter; import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch; import net.fabricmc.mappingio.format.tiny.Tiny2FileReader; import net.fabricmc.mappingio.format.tiny.Tiny2FileWriter; @@ -72,7 +71,7 @@ private static void mergeAndSaveMappings(Path from, Path out, IntermediateMappin } MemoryMappingTree officialTree = new MemoryMappingTree(); - MappingNsCompleter nsCompleter = new MappingNsCompleter(officialTree, Map.of(MappingsNamespace.OFFICIAL.toString(), MappingsNamespace.INTERMEDIARY.toString())); + UnobfuscatedMappingNsCompleter nsCompleter = new UnobfuscatedMappingNsCompleter(officialTree, MappingsNamespace.NAMED.toString(), Map.of(MappingsNamespace.OFFICIAL.toString(), MappingsNamespace.INTERMEDIARY.toString())); MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(nsCompleter, MappingsNamespace.OFFICIAL.toString()); intermediaryTree.accept(nsSwitch); @@ -92,7 +91,7 @@ private static void legacyMergeAndSaveMappings(Path from, Path out, Intermediate } MemoryMappingTree officialTree = new MemoryMappingTree(); - MappingNsCompleter nsCompleter = new MappingNsCompleter(officialTree, Map.of(MappingsNamespace.CLIENT_OFFICIAL.toString(), MappingsNamespace.INTERMEDIARY.toString(), MappingsNamespace.SERVER_OFFICIAL.toString(), MappingsNamespace.INTERMEDIARY.toString())); + UnobfuscatedMappingNsCompleter nsCompleter = new UnobfuscatedMappingNsCompleter(officialTree, MappingsNamespace.NAMED.toString(), Map.of(MappingsNamespace.CLIENT_OFFICIAL.toString(), MappingsNamespace.INTERMEDIARY.toString(), MappingsNamespace.SERVER_OFFICIAL.toString(), MappingsNamespace.INTERMEDIARY.toString())); intermediaryTree.accept(nsCompleter); // versions this old strip inner class attributes diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/tiny/UnobfuscatedMappingNsCompleter.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/tiny/UnobfuscatedMappingNsCompleter.java new file mode 100644 index 000000000..f51133fc5 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/tiny/UnobfuscatedMappingNsCompleter.java @@ -0,0 +1,231 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.mappings.tiny; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.jetbrains.annotations.Nullable; + +import net.fabricmc.mappingio.MappedElementKind; +import net.fabricmc.mappingio.MappingVisitor; +import net.fabricmc.mappingio.adapter.ForwardingMappingVisitor; + +/** + * Adapted from {@link net.fabricmc.mappingio.adapter.MappingNsCompleter}. + * This visitor completes any empty namespace with some alternative namespace + * only if that alternative namespace is equal to some test namespace. + * Mostly this will be used to complete official namespaces with intermediary + * names only if those intermediary names are equal to the named names. + */ +public final class UnobfuscatedMappingNsCompleter extends ForwardingMappingVisitor { + private final String testNs; + private final Map<String, String> alternatives; + private int testNsId; + private int[] alternativesMapping; + + private String srcName; + private String[] dstNames; + private boolean[] unobf; + private boolean[] lastMethodUnobf; + + private boolean relayHeaderOrMetadata; + + public UnobfuscatedMappingNsCompleter(MappingVisitor next, String testNs, Map<String, String> alternatives) { + super(next); + + this.testNs = testNs; + this.alternatives = alternatives; + } + + @Override + public boolean visitHeader() throws IOException { + relayHeaderOrMetadata = next.visitHeader(); + + return true; + } + + @Override + public void visitNamespaces(String srcNamespace, List<String> dstNamespaces) throws IOException { + int count = dstNamespaces.size(); + testNsId = -1; + alternativesMapping = new int[count]; + dstNames = new String[count]; + unobf = new boolean[count + 1]; // contains src ns as well + lastMethodUnobf = new boolean[count + 1]; // contains src ns as well + + for (int i = 0; i < count; i++) { + String dst = dstNamespaces.get(i); + + if (testNs.equals(dst)) { + testNsId = i; + } + + String src = alternatives.get(dst); + int srcIdx; + + if (src == null) { + srcIdx = i; + } else if (src.equals(srcNamespace)) { + srcIdx = -1; + } else { + srcIdx = dstNamespaces.indexOf(src); + if (srcIdx < 0) throw new RuntimeException("invalid alternative mapping ns "+src+": not in "+dstNamespaces+" or "+srcNamespace); + } + + alternativesMapping[i] = srcIdx; + } + + if (testNsId == -1 && !testNs.equals(srcNamespace)) throw new RuntimeException("test namespace " + testNs + " not present in src and dst namespaces!"); + + if (relayHeaderOrMetadata) next.visitNamespaces(srcNamespace, dstNamespaces); + } + + @Override + public void visitMetadata(String key, @Nullable String value) throws IOException { + if (relayHeaderOrMetadata) next.visitMetadata(key, value); + } + + @Override + public boolean visitContent() throws IOException { + relayHeaderOrMetadata = true; // for in-content metadata + + return next.visitContent(); + } + + @Override + public boolean visitClass(String srcName) throws IOException { + this.srcName = srcName; + + return next.visitClass(srcName); + } + + @Override + public boolean visitField(String srcName, @Nullable String srcDesc) throws IOException { + this.srcName = srcName; + + return next.visitField(srcName, srcDesc); + } + + @Override + public boolean visitMethod(String srcName, @Nullable String srcDesc) throws IOException { + this.srcName = srcName; + + return next.visitMethod(srcName, srcDesc); + } + + @Override + public boolean visitMethodArg(int argPosition, int lvIndex, @Nullable String srcName) throws IOException { + this.srcName = srcName; + + return next.visitMethodArg(argPosition, lvIndex, srcName); + } + + @Override + public boolean visitMethodVar(int lvtRowIndex, int lvIndex, int startOpIdx, int endOpIdx, @Nullable String srcName) throws IOException { + this.srcName = srcName; + + return next.visitMethodVar(lvtRowIndex, lvIndex, startOpIdx, endOpIdx, srcName); + } + + @Override + public void visitDstName(MappedElementKind targetKind, int namespace, String name) { + dstNames[namespace] = name; + } + + @Override + public boolean visitElementContent(MappedElementKind targetKind) throws IOException { + for (int ns : alternativesMapping) { + int idx = ns + 1; // offset by 1 bc src ns is -1 + + if (targetKind == MappedElementKind.METHOD_ARG || targetKind == MappedElementKind.METHOD_VAR) { + unobf[idx] = lastMethodUnobf[idx]; + } else if (ns == testNsId) { + unobf[idx] = true; + + if (targetKind == MappedElementKind.METHOD) { + lastMethodUnobf[idx] = true; + } + } else if (!unobf[idx]) { // only check each ns once + String name = ns == -1 ? srcName : dstNames[ns]; + String testName = dstNames[testNsId]; + + if (testName != null && testName.equals(name)) { + unobf[idx] = true; + + if (targetKind == MappedElementKind.METHOD) { + lastMethodUnobf[idx] = true; + } + } + } + } + + nsLoop: for (int i = 0; i < dstNames.length; i++) { + String name = dstNames[i]; + + if (name == null) { + int src = i; + long visited = 1L << src; + + do { + int newSrc = alternativesMapping[src]; + + if (newSrc < 0) { // mapping to src name + if (unobf[newSrc + 1]) { + name = srcName; + break; // srcName must never be null + } else { + continue nsLoop; + } + } else if (newSrc == src) { // no-op (identity) mapping, explicit in case src > 64 + continue nsLoop; // always null + } else if ((visited & 1L << newSrc) != 0) { // cyclic mapping + continue nsLoop; // always null + } else { + if (unobf[newSrc + 1]) { + src = newSrc; + name = dstNames[src]; + visited |= 1L << src; + } else { + continue nsLoop; + } + } + } while (name == null); + + assert name != null; + } + + next.visitDstName(targetKind, i, name); + } + + Arrays.fill(dstNames, null); + Arrays.fill(unobf, false); + Arrays.fill(lastMethodUnobf, false); + + return next.visitElementContent(targetKind); + } +} From 481ad6d00e4b2679b83789d338c564af62108a2f Mon Sep 17 00:00:00 2001 From: Space Walker <spacedoesrs@gmail.com> Date: Wed, 11 Sep 2024 19:39:08 +0200 Subject: [PATCH 2/3] TinyRemapperHelper.create does not like null names --- .../loom/util/TinyRemapperHelper.java | 43 ++++++++++++++++--- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java b/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java index 62d722889..4024d4bfc 100644 --- a/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java +++ b/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java @@ -111,22 +111,51 @@ public static IMappingProvider create(MappingTree mappings, String from, String for (MappingTree.ClassMapping classDef : mappings.getClasses()) { String className = classDef.getName(fromId); - String dstName = classDef.getName(toId); - if (dstName == null) { + if (className == null) { + continue; + } + + String dstClassName = classDef.getName(toId); + + if (dstClassName == null) { // Unsure if this is correct, should be better than crashing tho. - dstName = className; + dstClassName = className; } - acceptor.acceptClass(className, dstName); + acceptor.acceptClass(className, dstClassName); for (MappingTree.FieldMapping field : classDef.getFields()) { - acceptor.acceptField(memberOf(className, field.getName(fromId), field.getDesc(fromId)), field.getName(toId)); + String fieldName = field.getName(fromId); + + if (fieldName == null) { + continue; + } + + String dstFieldName = field.getName(toId); + + if (dstFieldName == null) { + dstFieldName = fieldName; + } + + acceptor.acceptField(memberOf(className, fieldName, field.getDesc(fromId)), dstFieldName); } for (MappingTree.MethodMapping method : classDef.getMethods()) { - IMappingProvider.Member methodIdentifier = memberOf(className, method.getName(fromId), method.getDesc(fromId)); - acceptor.acceptMethod(methodIdentifier, method.getName(toId)); + String methodName = method.getName(fromId); + + if (methodName == null) { + continue; + } + + String dstMethodName = method.getName(toId); + + if (dstMethodName == null) { + dstMethodName = methodName; + } + + IMappingProvider.Member methodIdentifier = memberOf(className, methodName, method.getDesc(fromId)); + acceptor.acceptMethod(methodIdentifier, dstMethodName); if (remapLocalVariables) { for (MappingTree.MethodArgMapping parameter : method.getArgs()) { From 3bb19d035da5866dba013f818f2d5a835ec1c0b3 Mon Sep 17 00:00:00 2001 From: Space Walker <spacedoesrs@gmail.com> Date: Fri, 20 Sep 2024 19:30:31 +0200 Subject: [PATCH 3/3] add MappingsMergerTest --- .../mappings/tiny/MappingsMerger.java | 7 +- .../fabricmc/loom/test/unit/LoomMocks.groovy | 18 ++ .../loom/test/unit/MappingsMergerTest.groovy | 291 ++++++++++++++++++ 3 files changed, 314 insertions(+), 2 deletions(-) create mode 100644 src/test/groovy/net/fabricmc/loom/test/unit/MappingsMergerTest.groovy diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/tiny/MappingsMerger.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/tiny/MappingsMerger.java index 07207e620..5827a437d 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/tiny/MappingsMerger.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/tiny/MappingsMerger.java @@ -34,6 +34,7 @@ import java.util.regex.Pattern; import com.google.common.base.Stopwatch; +import org.jetbrains.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -62,7 +63,8 @@ public static void mergeAndSaveMappings(Path from, Path out, MinecraftProvider m LOGGER.info(":merged mappings in " + stopwatch.stop()); } - private static void mergeAndSaveMappings(Path from, Path out, IntermediateMappingsService intermediateMappingsService) throws IOException { + @VisibleForTesting + public static void mergeAndSaveMappings(Path from, Path out, IntermediateMappingsService intermediateMappingsService) throws IOException { MemoryMappingTree intermediaryTree = new MemoryMappingTree(); intermediateMappingsService.getMemoryMappingTree().accept(new MappingSourceNsSwitch(intermediaryTree, MappingsNamespace.INTERMEDIARY.toString())); @@ -82,7 +84,8 @@ private static void mergeAndSaveMappings(Path from, Path out, IntermediateMappin } } - private static void legacyMergeAndSaveMappings(Path from, Path out, IntermediateMappingsService intermediateMappingsService) throws IOException { + @VisibleForTesting + public static void legacyMergeAndSaveMappings(Path from, Path out, IntermediateMappingsService intermediateMappingsService) throws IOException { MemoryMappingTree intermediaryTree = new MemoryMappingTree(); intermediateMappingsService.getMemoryMappingTree().accept(intermediaryTree); diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/LoomMocks.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/LoomMocks.groovy index 1e0c41161..dd417fe98 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/LoomMocks.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/LoomMocks.groovy @@ -24,9 +24,11 @@ package net.fabricmc.loom.test.unit +import java.nio.file.Path import java.util.function.Function import net.fabricmc.loom.configuration.providers.mappings.IntermediaryMappingsProvider +import net.fabricmc.loom.configuration.providers.mappings.IntermediateMappingsService import net.fabricmc.loom.test.util.GradleTestUtil import net.fabricmc.loom.util.download.Download @@ -49,4 +51,20 @@ class LoomMocks { when(mock.getRefreshDeps()).thenReturn(refreshDeps) return mock } + + static IntermediateMappingsService.Options intermediateMappingsServiceOptionsMock(Path intermediaryTiny, String expectedSrcNs) { + def intermediaryTinyProperty = GradleTestUtil.mockProperty(intermediaryTiny) + def expectedSrcNsProperty = GradleTestUtil.mockProperty(expectedSrcNs) + + def mock = spy(IntermediateMappingsService.Options.class) + when(mock.getIntermediaryTiny()).thenReturn(intermediaryTinyProperty) + when(mock.getExpectedSrcNs()).thenReturn(expectedSrcNsProperty) + return mock + } + + static IntermediateMappingsService intermediateMappingsServiceMock(IntermediateMappingsService.Options options) { + def mock = spy(IntermediateMappingsService.class) + when(mock.getOptions()).thenReturn(options) + return mock + } } diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/MappingsMergerTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/MappingsMergerTest.groovy new file mode 100644 index 000000000..8bf8db99c --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/MappingsMergerTest.groovy @@ -0,0 +1,291 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.test.unit + +import java.nio.file.Files +import java.nio.file.Path + +import spock.lang.TempDir + +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace +import net.fabricmc.loom.configuration.providers.mappings.IntermediateMappingsService +import net.fabricmc.loom.configuration.providers.mappings.tiny.MappingsMerger +import net.fabricmc.mappingio.MappingReader +import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch +import net.fabricmc.mappingio.tree.MemoryMappingTree + +import static org.junit.jupiter.api.Assertions.* + +class MappingsMergerTest { + @TempDir + Path tempDir + + def "mappings merger"() { + given: + Path intermediaryTiny = tempDir.resolve("intermediary.tiny") + Path mappingsTiny = tempDir.resolve("mappings.tiny") + Path mergedMappingsTiny = tempDir.resolve("merged_mappings.tiny") + + Files.writeString(intermediaryTiny, INTERMEDIARY_MAPPINGS) + Files.writeString(mappingsTiny, NAMED_MAPPINGS) + + IntermediateMappingsService.Options intermediateMappingsServiceOptions = LoomMocks.intermediateMappingsServiceOptionsMock(intermediaryTiny, OFFICIAL) + IntermediateMappingsService intermediateMappingsService = LoomMocks.intermediateMappingsServiceMock(intermediateMappingsServiceOptions) + + when: + MappingsMerger.mergeAndSaveMappings(mappingsTiny, mergedMappingsTiny, intermediateMappingsService) + + def mappings = new MemoryMappingTree() + MappingReader.read(mergedMappingsTiny, mappings) + + then: + mappings.srcNamespace == OFFICIAL + mappings.dstNamespaces == [INTERMEDIARY, NAMED] + def namedNs = mappings.getNamespaceId(NAMED) + mappings.classes.size() == 2 + mappings.classes[0].srcName == "a" + mappings.classes[0].getDstName(namedNs) == "net/fabricmc/loom/test/unit/ObfuscatedClass" + mappings.classes[0].comment == "class comment" + mappings.classes[0].fields.size() == 1 + mappings.classes[0].fields[0].srcName == "a" + mappings.classes[0].fields[0].getDstDesc(namedNs) == "obfuscatedField" + mappings.classes[0].fields[0].comment == "field comment" + mappings.classes[0].methods.size() == 1 + mappings.classes[0].methods[0].srcName == "a" + mappings.classes[0].methods[0].getDstDesc(namedNs) == "obfuscatedMethod" + mappings.classes[0].methods[0].comment == "method comment" + mappings.classes[0].methods[0].args.size() == 1 + mappings.classes[0].methods[1].args[0].getDstName(namedNs) == "obfuscatedMethodParameter" + mappings.classes[1].srcName == "net/fabricmc/loom/test/unit/UnobfuscatedClass" + mappings.classes[1].getDstName(namedNs) == "net/fabricmc/loom/test/unit/UnobfuscatedClass" + mappings.classes[1].comment == "class comment" + mappings.classes[1].fields.size() == 1 + mappings.classes[1].fields[0].srcName == "unobfuscatedField" + mappings.classes[1].fields[0].getDstDesc(namedNs) == "unobfuscatedField" + mappings.classes[1].fields[0].comment == "field comment" + mappings.classes[1].methods.size() == 1 + mappings.classes[1].methods[0].srcName == "unobfuscatedMethod" + mappings.classes[1].methods[0].getDstDesc(namedNs) == "unobfuscatedMethod" + mappings.classes[1].methods[0].comment == "method comment" + mappings.classes[1].methods[0].args.size() == 1 + mappings.classes[1].methods[1].args[0].getDstName(namedNs) == "unobfuscatedMethodParameter" + } + + def "mappings merger legacy"() { + given: + Path intermediaryTiny = tempDir.resolve("intermediary.tiny") + Path mappingsTiny = tempDir.resolve("mappings.tiny") + Path mergedMappingsTiny = tempDir.resolve("merged_mappings.tiny") + + Files.writeString(intermediaryTiny, LEGACY_INTERMEDIARY_MAPPINGS) + Files.writeString(mappingsTiny, LEGACY_NAMED_MAPPINGS) + + IntermediateMappingsService.Options intermediateMappingsServiceOptions = LoomMocks.intermediateMappingsServiceOptionsMock(intermediaryTiny, INTERMEDIARY) + IntermediateMappingsService intermediateMappingsService = LoomMocks.intermediateMappingsServiceMock(intermediateMappingsServiceOptions) + + when: + MappingsMerger.legacyMergeAndSaveMappings(mappingsTiny, mergedMappingsTiny, intermediateMappingsService) + + def mappings = new MemoryMappingTree() + MappingReader.read(mergedMappingsTiny, mappings) + + def clientMappings = new MemoryMappingTree() + def serverMappings = new MemoryMappingTree() + + mappings.accept(new MappingSourceNsSwitch(clientMappings, CLIENT_OFFICIAL, true)) + mappings.accept(new MappingSourceNsSwitch(serverMappings, SERVER_OFFICIAL, true)) + + then: + clientMappings.srcNamespace == CLIENT_OFFICIAL + clientMappings.dstNamespaces == [ + INTERMEDIARY, + SERVER_OFFICIAL, + NAMED + ] + def clientNamedNs = clientMappings.getNamespaceId(NAMED) + clientMappings.classes.size() == 3 + clientMappings.classes[0].srcName == "a" + clientMappings.classes[0].getDstName(namedNs) == "net/fabricmc/loom/test/unit/CommonObfuscatedClass" + clientMappings.classes[0].comment == "class comment" + clientMappings.classes[0].fields.size() == 1 + clientMappings.classes[0].fields[0].srcName == "a" + clientMappings.classes[0].fields[0].getDstDesc(namedNs) == "commonObfuscatedField" + clientMappings.classes[0].fields[0].comment == "field comment" + clientMappings.classes[0].methods.size() == 1 + clientMappings.classes[0].methods[0].srcName == "a" + clientMappings.classes[0].methods[0].getDstDesc(namedNs) == "commonObfuscatedMethod" + clientMappings.classes[0].methods[0].comment == "method comment" + clientMappings.classes[0].methods[0].args.size() == 1 + clientMappings.classes[0].methods[1].args[0].getDstName(namedNs) == "commonObfuscatedMethodParameter" + clientMappings.classes[1].srcName == "b" + clientMappings.classes[1].getDstName(namedNs) == "net/fabricmc/loom/test/unit/ClientObfuscatedClass" + clientMappings.classes[1].comment == "class comment" + clientMappings.classes[1].fields.size() == 1 + clientMappings.classes[1].fields[0].srcName == "a" + clientMappings.classes[1].fields[0].getDstDesc(namedNs) == "clientObfuscatedField" + clientMappings.classes[1].fields[0].comment == "field comment" + clientMappings.classes[1].methods.size() == 1 + clientMappings.classes[1].methods[0].srcName == "a" + clientMappings.classes[1].methods[0].getDstDesc(namedNs) == "clientObfuscatedMethod" + clientMappings.classes[1].methods[0].comment == "method comment" + clientMappings.classes[1].methods[0].args.size() == 1 + clientMappings.classes[1].methods[1].args[0].getDstName(namedNs) == "clientObfuscatedMethodParameter" + clientMappings.classes[2].srcName == "net/fabricmc/loom/test/unit/UnobfuscatedClass" + clientMappings.classes[2].getDstName(namedNs) == "net/fabricmc/loom/test/unit/UnobfuscatedClass" + clientMappings.classes[2].comment == "class comment" + clientMappings.classes[2].fields.size() == 1 + clientMappings.classes[2].fields[0].srcName == "unobfuscatedField" + clientMappings.classes[2].fields[0].getDstDesc(namedNs) == "unobfuscatedField" + clientMappings.classes[2].fields[0].comment == "field comment" + clientMappings.classes[2].methods.size() == 1 + clientMappings.classes[2].methods[0].srcName == "unobfuscatedMethod" + clientMappings.classes[2].methods[0].getDstDesc(namedNs) == "unobfuscatedMethod" + clientMappings.classes[2].methods[0].comment == "method comment" + clientMappings.classes[2].methods[0].args.size() == 1 + clientMappings.classes[2].methods[1].args[0].getDstName(namedNs) == "unobfuscatedMethodParameter" + + serverMappings.srcNamespace == SERVER_OFFICIAL + serverMappings.dstNamespaces == [ + INTERMEDIARY, + CLIENT_OFFICIAL, + NAMED + ] + def serverNamedNs = serverMappings.getNamespaceId(NAMED) + serverMappings.classes.size() == 3 + serverMappings.classes[0].srcName == "a" + serverMappings.classes[0].getDstName(namedNs) == "net/fabricmc/loom/test/unit/CommonObfuscatedClass" + serverMappings.classes[0].comment == "class comment" + serverMappings.classes[0].fields.size() == 1 + serverMappings.classes[0].fields[0].srcName == "a" + serverMappings.classes[0].fields[0].getDstDesc(namedNs) == "commonObfuscatedField" + serverMappings.classes[0].fields[0].comment == "field comment" + serverMappings.classes[0].methods.size() == 1 + serverMappings.classes[0].methods[0].srcName == "a" + serverMappings.classes[0].methods[0].getDstDesc(namedNs) == "commonObfuscatedMethod" + serverMappings.classes[0].methods[0].comment == "method comment" + serverMappings.classes[0].methods[0].args.size() == 1 + serverMappings.classes[0].methods[1].args[0].getDstName(namedNs) == "commonObfuscatedMethodParameter" + serverMappings.classes[1].srcName == "b" + serverMappings.classes[1].getDstName(namedNs) == "net/fabricmc/loom/test/unit/ClientObfuscatedClass" + serverMappings.classes[1].comment == "class comment" + serverMappings.classes[1].fields.size() == 1 + serverMappings.classes[1].fields[0].srcName == "a" + serverMappings.classes[1].fields[0].getDstDesc(namedNs) == "clientObfuscatedField" + serverMappings.classes[1].fields[0].comment == "field comment" + serverMappings.classes[1].methods.size() == 1 + serverMappings.classes[1].methods[0].srcName == "a" + serverMappings.classes[1].methods[0].getDstDesc(namedNs) == "clientObfuscatedMethod" + serverMappings.classes[1].methods[0].comment == "method comment" + serverMappings.classes[1].methods[0].args.size() == 1 + serverMappings.classes[1].methods[1].args[0].getDstName(namedNs) == "clientObfuscatedMethodParameter" + serverMappings.classes[2].srcName == "net/fabricmc/loom/test/unit/UnobfuscatedClass" + serverMappings.classes[2].getDstName(namedNs) == "net/fabricmc/loom/test/unit/UnobfuscatedClass" + serverMappings.classes[2].comment == "class comment" + serverMappings.classes[2].fields.size() == 1 + serverMappings.classes[2].fields[0].srcName == "unobfuscatedField" + serverMappings.classes[2].fields[0].getDstDesc(namedNs) == "unobfuscatedField" + serverMappings.classes[2].fields[0].comment == "field comment" + serverMappings.classes[2].methods.size() == 1 + serverMappings.classes[2].methods[0].srcName == "unobfuscatedMethod" + serverMappings.classes[2].methods[0].getDstDesc(namedNs) == "unobfuscatedMethod" + serverMappings.classes[2].methods[0].comment == "method comment" + serverMappings.classes[2].methods[0].args.size() == 1 + serverMappings.classes[2].methods[1].args[0].getDstName(namedNs) == "unobfuscatedMethodParameter" + } + + private static final String OFFICIAL = MappingsNamespace.OFFICIAL.toString() + private static final String CLIENT_OFFICIAL = MappingsNamespace.CLIENT_OFFICIAL.toString() + private static final String SERVER_OFFICIAL = MappingsNamespace.SERVER_OFFICIAL.toString() + private static final String INTERMEDIARY = MappingsNamespace.INTERMEDIARY.toString() + private static final String NAMED = MappingsNamespace.NAMED.toString() + + private static final String INTERMEDIARY_MAPPINGS = """ +tiny\t2\t0\tofficial\tintermediary +c\ta\tclass_1 +\tf\tZ\ta\tfield_1 +\tm\t(Z)V\ta\tmethod_1 +""".trim() + private static final String NAMED_MAPPINGS = """ +tiny\t2\t0\tintermediary\tnamed +c\tclass_1\tnet/fabricmc/loom/test/unit/ObfuscatedClass +\tc\tclass comment +\tf\tZ\tfield_1\tobfuscatedField +\t\tc\tfield comment +\tm\t(Z)V\tmethod_1\tobfuscatedMethod +\t\tc\tmethod comment +\t\tp\t0\t\t\tobfuscatedMethodParameter +c\tnet/fabricmc/loom/test/unit/UnobfuscatedClass\tnet/fabricmc/loom/test/unit/UnobfuscatedClass +\tc\tclass comment +\tf\tZ\tunobfuscatedField\tunobfuscatedField +\t\tc\tfield comment +\tm\t(Z)V\tunobfuscatedMethod\tunobfuscatedMethod +\t\tc\tmethod comment +\t\tp\t0\t\t\tunobfuscatedMethodParameter +""".trim() + + private static final String LEGACY_INTERMEDIARY_MAPPINGS = """ +tiny\t2\t0\tintermediary\tclientOfficial\tserverOfficial +c\tclass_1\ta\ta +\tf\tZ\tfield_1\ta\ta +\tm\t(Z)V\tmethod_1\ta\ta +c\tclass_2\tc\t +\tf\tZ\tfield_2\ta\t +\tm\t(Z)V\tmethod_2\ta\t +c\tclass_3\t\tc +\tf\tZ\tfield_3\t\ta +\tm\t(Z)V\tmethod_3\t\ta +""".trim() + private static final String LEGACY_NAMED_MAPPINGS = """ +tiny\t2\t0\tintermediary\tnamed +c\tclass_1\tnet/fabricmc/loom/test/unit/CommonObfuscatedClass +\tc\tclass comment +\tf\tZ\tfield_1\tcommonObfuscatedField +\t\tc\tfield comment +\tm\t(Z)V\tmethod_1\tcommonObfuscatedMethod +\t\tc\tmethod comment +\t\tp\t0\t\t\tcommonObfuscatedMethodParameter +c\tclass_2\tnet/fabricmc/loom/test/unit/ClientObfuscatedClass +\tc\tclass comment +\tf\tZ\tfield_2\tclientObfuscatedField +\t\tc\tfield comment +\tm\t(Z)V\tmethod_2\tclientObfuscatedMethod +\t\tc\tmethod comment +\t\tp\t0\t\t\tclientObfuscatedMethodParameter +c\tclass_3\tnet/fabricmc/loom/test/unit/ServerObfuscatedClass +\tc\tclass comment +\tf\tZ\tfield_3\tserverObfuscatedField +\t\tc\tfield comment +\tm\t(Z)V\tmethod_3\tserverObfuscatedMethod +\t\tc\tmethod comment +\t\tp\t0\t\t\tserverObfuscatedMethodParameter +c\tnet/fabricmc/loom/test/unit/UnobfuscatedClass\tnet/fabricmc/loom/test/unit/UnobfuscatedClass +\tc\tclass comment +\tf\tZ\tunobfuscatedField\tunobfuscatedField +\t\tc\tfield comment +\tm\t(Z)V\tunobfuscatedMethod\tunobfuscatedMethod +\t\tc\tmethod comment +\t\tp\t0\t\t\tunobfuscatedMethodParameter +""".trim() +}