Skip to content

Commit 5769aa7

Browse files
Merge branch 'master' into develop/2.8
2 parents 34434dc + da9e26d commit 5769aa7

File tree

8 files changed

+155
-20
lines changed

8 files changed

+155
-20
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,3 +486,12 @@ Blood, sweat, refactors, enhancements, tears, unit tests, bugfixes, blood, and m
486486
VINEFLOWERRRRRRRRRR
487487

488488
- downgrade vineflower to 1.11.1 to fix issues with decompilation ([#341](https://github.com/QuiltMC/enigma/pull/341))
489+
490+
# 2.7.2
491+
492+
version `2.7.2`, or "your beloved enigma team proves once again that they are professionals at introducing performance issues".
493+
494+
- fix memory overuse issues introduced with the library index changes ([#345](https://github.com/QuiltMC/enigma/pull/345))
495+
- indexes are now better combined internally to save memory
496+
- define the iteration order for `InheritanceIndex#getAncestors(ClassEntry)` as breadth-first ([#347](https://github.com/QuiltMC/enigma/pull/347))
497+
- add `InheritanceIndex#streamAncestors(ClassEntry)`

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ subprojects {
3333

3434
group = 'org.quiltmc'
3535
// When bumping version, remember to update the int constants in Enigma!
36-
version = '2.7.1'
36+
version = '2.7.2'
3737

3838
var ENV = System.getenv()
3939
version = version + (ENV.GITHUB_ACTIONS ? (ENV.SNAPSHOTS_URL ? "-SNAPSHOT" : "") : "+local")

enigma/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,8 @@ final testRecommendedImplPluginAgainstBumpedEnigma = tasks
376376
}
377377
}
378378

379-
test.dependsOn(obfuscateTestInputs, testPreHandlingPluginAgainstCurrentEnigma, testRecommendedImplPluginAgainstBumpedEnigma)
379+
test.dependsOn(obfuscateTestInputs)
380+
check.dependsOn(testPreHandlingPluginAgainstCurrentEnigma, testRecommendedImplPluginAgainstBumpedEnigma)
380381

381382
publishing {
382383
publications {

enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public class Enigma {
8282
* {@link EnigmaPlugin#supportsEnigmaVersion(Version)} implementations,
8383
* allowing plugins to 'remember' the version of Enigma they were built with.
8484
*/
85-
public static final int PATCH_VERSION = 1;
85+
public static final int PATCH_VERSION = 2;
8686

8787
private static final Version CURRENT_VERSION = new Version(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION);
8888

enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/InheritanceIndex.java

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@
22

33
import com.google.common.collect.HashMultimap;
44
import com.google.common.collect.Multimap;
5-
import com.google.common.collect.Sets;
65
import org.quiltmc.enigma.api.translation.representation.entry.ClassDefEntry;
76
import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry;
7+
import org.quiltmc.enigma.util.Utils;
88

99
import java.util.Collection;
1010
import java.util.HashSet;
11+
import java.util.LinkedHashSet;
1112
import java.util.LinkedList;
1213
import java.util.Set;
14+
import java.util.stream.Stream;
15+
16+
import static java.util.stream.Collectors.toCollection;
1317

1418
public class InheritanceIndex implements JarIndexer {
1519
private final EntryIndex entryIndex;
@@ -63,21 +67,52 @@ public Collection<ClassEntry> getDescendants(ClassEntry classEntry) {
6367
return descendants;
6468
}
6569

70+
/**
71+
* Prefer {@link #streamAncestors(ClassEntry)} if:
72+
* <ul>
73+
* <li> performing a search that may terminate before examining all ancestors
74+
* <li> the creation of a {@link Set} is unnecessary
75+
* <li> duplicate occurrences of interfaces implemented by multiple ancestors are required
76+
* </ul>
77+
*
78+
* @return a {@link Set} containing the passed {@code classEntry}'s ancestors
79+
*
80+
* @implSpec The returned set has breadth-first iteration order.<br>
81+
* Only the first (shallowest) occurrence of an interface implemented by multiple ancestors is included.<br>
82+
* Only the first (shallowest) occurrence of {@code java.lang.Object} is included.
83+
*
84+
* @implNote No guarantees are made about the order within a generation of ancestors.
85+
*
86+
* @see #streamAncestors(ClassEntry)
87+
*/
6688
public Set<ClassEntry> getAncestors(ClassEntry classEntry) {
67-
Set<ClassEntry> ancestors = Sets.newHashSet();
68-
69-
LinkedList<ClassEntry> ancestorQueue = new LinkedList<>();
70-
ancestorQueue.push(classEntry);
71-
72-
while (!ancestorQueue.isEmpty()) {
73-
ClassEntry ancestor = ancestorQueue.pop();
74-
Collection<ClassEntry> parents = this.getParents(ancestor);
89+
return this.streamAncestors(classEntry).collect(toCollection(LinkedHashSet::new));
90+
}
7591

76-
parents.forEach(ancestorQueue::push);
77-
ancestors.addAll(parents);
78-
}
92+
/**
93+
* @return a {@link Stream} of the passed {@code classEntry}'s ancestors in breadth-first order
94+
*
95+
* @implSpec Interfaces implemented by multiple ancestors will occur multiple times in the stream
96+
* (see {@link Stream#distinct()} and {@link #getAncestors(ClassEntry)}).<br>
97+
* {@code java.lang.Object} will occur once for each interface.
98+
*
99+
* @implNote No guarantees are made about the order within a generation of ancestors.
100+
*
101+
* @see #getAncestors(ClassEntry)
102+
*/
103+
public Stream<ClassEntry> streamAncestors(ClassEntry classEntry) {
104+
return this.streamAncestorsImpl(this.getParents(classEntry));
105+
}
79106

80-
return ancestors;
107+
private Stream<ClassEntry> streamAncestorsImpl(Collection<ClassEntry> generation) {
108+
return generation.isEmpty() ? Stream.empty() : Utils.lazyConcat(
109+
generation::stream,
110+
() -> this.streamAncestorsImpl(generation.stream()
111+
.map(this::getParents)
112+
.flatMap(Collection::stream)
113+
.toList()
114+
)
115+
);
81116
}
82117

83118
public Relation computeClassRelation(ClassEntry classEntry, ClassEntry potentialAncestor) {

enigma/src/main/java/org/quiltmc/enigma/util/Utils.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.concurrent.TimeUnit;
2222
import java.util.concurrent.locks.Lock;
2323
import java.util.function.Supplier;
24+
import java.util.stream.Stream;
2425
import java.util.zip.ZipEntry;
2526
import java.util.zip.ZipFile;
2627

@@ -196,4 +197,9 @@ public static float clamp(double value, float min, float max) {
196197
public static double clamp(double value, double min, double max) {
197198
return Math.min(max, Math.max(value, min));
198199
}
200+
201+
@SafeVarargs
202+
public static <T> Stream<T> lazyConcat(Supplier<Stream<? extends T>>... streamers) {
203+
return Stream.of(streamers).flatMap(Supplier::get);
204+
}
199205
}

enigma/src/test/java/org/quiltmc/enigma/TestJarIndexInheritanceTree.java

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import java.nio.file.Path;
2525
import java.util.Collection;
26+
import java.util.List;
2627

2728
import static org.hamcrest.MatcherAssert.assertThat;
2829
import static org.hamcrest.Matchers.contains;
@@ -35,9 +36,9 @@ public class TestJarIndexInheritanceTree {
3536

3637
private static final ClassEntry OBJECT_CLASS = TestEntryFactory.newClass("java/lang/Object");
3738
private static final ClassEntry BASE_CLASS = TestEntryFactory.newClass("a");
38-
private static final ClassEntry SUB_CLASS_A = TestEntryFactory.newClass("b");
39-
private static final ClassEntry SUB_CLASS_AA = TestEntryFactory.newClass("d");
40-
private static final ClassEntry SUB_CLASS_B = TestEntryFactory.newClass("c");
39+
private static final ClassEntry SUB_CLASS_A = TestEntryFactory.newClass("c");
40+
private static final ClassEntry SUB_CLASS_AA = TestEntryFactory.newClass("e");
41+
private static final ClassEntry SUB_CLASS_B = TestEntryFactory.newClass("d");
4142
private static final FieldEntry NAME_FIELD = TestEntryFactory.newField(BASE_CLASS, "a", "Ljava/lang/String;");
4243
private static final FieldEntry NUM_THINGS_FIELD = TestEntryFactory.newField(SUB_CLASS_B, "a", "I");
4344

@@ -52,7 +53,12 @@ public TestJarIndexInheritanceTree() throws Exception {
5253
@Test
5354
public void obfEntries() {
5455
assertThat(this.index.getIndex(EntryIndex.class).getClasses(), Matchers.containsInAnyOrder(
55-
TestEntryFactory.newClass("org/quiltmc/enigma/input/Keep"), BASE_CLASS, SUB_CLASS_A, SUB_CLASS_AA, SUB_CLASS_B
56+
TestEntryFactory.newClass("org/quiltmc/enigma/input/Keep"),
57+
BASE_CLASS, SUB_CLASS_A, SUB_CLASS_AA, SUB_CLASS_B,
58+
InterfaceTree.OUTER,
59+
InterfaceTree.ROOT,
60+
InterfaceTree.BRANCH_1_2, InterfaceTree.BRANCH_3_4,
61+
InterfaceTree.LEAF_1, InterfaceTree.LEAF_2, InterfaceTree.LEAF_3, InterfaceTree.LEAF_4
5662
));
5763
}
5864

@@ -216,4 +222,65 @@ public void containsEntries() {
216222
assertThat(entryIndex.hasMethod(TestEntryFactory.newMethod(SUB_CLASS_AA, "b", "()V")), is(false));
217223
assertThat(entryIndex.hasMethod(TestEntryFactory.newMethod(SUB_CLASS_B, "b", "()V")), is(true));
218224
}
225+
226+
@Test
227+
void streamAncestorsOrder() {
228+
final List<ClassEntry> ancestors = this.index.getIndex(InheritanceIndex.class)
229+
.streamAncestors(InterfaceTree.ROOT)
230+
.toList();
231+
232+
assertThat(ancestors.size(), is(13));
233+
234+
// First generation: Object appears once for ROOT
235+
assertThat(ancestors.subList(0, 3), Matchers.containsInAnyOrder(
236+
InterfaceTree.BRANCH_1_2, InterfaceTree.BRANCH_3_4, OBJECT_CLASS
237+
));
238+
239+
// Second generation: Object appears once for each of the 2 branches
240+
assertThat(ancestors.subList(3, 9), Matchers.containsInAnyOrder(
241+
InterfaceTree.LEAF_1, InterfaceTree.LEAF_2, InterfaceTree.LEAF_3, InterfaceTree.LEAF_4,
242+
OBJECT_CLASS, OBJECT_CLASS
243+
));
244+
245+
// Third generation: Object appears once for each of the 4 leaves
246+
assertThat(ancestors.subList(9, 13), Matchers.containsInAnyOrder(
247+
OBJECT_CLASS, OBJECT_CLASS, OBJECT_CLASS, OBJECT_CLASS
248+
));
249+
}
250+
251+
@Test
252+
void getAncestorsOrder() {
253+
final List<ClassEntry> ancestors = this.index.getIndex(InheritanceIndex.class)
254+
.getAncestors(InterfaceTree.ROOT)
255+
.stream()
256+
.toList();
257+
258+
assertThat(ancestors.size(), is(7));
259+
260+
// Only the first appearance of Object (from the root) is included
261+
assertThat(ancestors.subList(0, 3), Matchers.containsInAnyOrder(
262+
InterfaceTree.BRANCH_1_2, InterfaceTree.BRANCH_3_4, OBJECT_CLASS
263+
));
264+
265+
assertThat(ancestors.subList(3, 7), Matchers.containsInAnyOrder(
266+
InterfaceTree.LEAF_1, InterfaceTree.LEAF_2, InterfaceTree.LEAF_3, InterfaceTree.LEAF_4
267+
));
268+
}
269+
270+
private interface InterfaceTree {
271+
String OUTER_NAME = "b";
272+
String OUTER_PREFIX = OUTER_NAME + "$";
273+
274+
ClassEntry OUTER = TestEntryFactory.newClass(OUTER_NAME);
275+
276+
ClassEntry ROOT = TestEntryFactory.newClass(OUTER_PREFIX + "g");
277+
278+
ClassEntry BRANCH_1_2 = TestEntryFactory.newClass(OUTER_PREFIX + "a");
279+
ClassEntry BRANCH_3_4 = TestEntryFactory.newClass(OUTER_PREFIX + "b");
280+
281+
ClassEntry LEAF_1 = TestEntryFactory.newClass(OUTER_PREFIX + "c");
282+
ClassEntry LEAF_2 = TestEntryFactory.newClass(OUTER_PREFIX + "d");
283+
ClassEntry LEAF_3 = TestEntryFactory.newClass(OUTER_PREFIX + "e");
284+
ClassEntry LEAF_4 = TestEntryFactory.newClass(OUTER_PREFIX + "f");
285+
}
219286
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.quiltmc.enigma.input.inheritance_tree;
2+
3+
public class InterfaceTree {
4+
interface Root extends Branch12, Branch34 { }
5+
6+
interface Branch12 extends Leaf1, Leaf2 { }
7+
8+
interface Branch34 extends Leaf3, Leaf4 { }
9+
10+
interface Leaf1 { }
11+
12+
interface Leaf2 { }
13+
14+
interface Leaf3 { }
15+
16+
interface Leaf4 { }
17+
}

0 commit comments

Comments
 (0)