Skip to content

Commit 3582a26

Browse files
committed
Merge remote-tracking branch 'danyadev/optimize-transformers'
2 parents c34d409 + 17619ee commit 3582a26

6 files changed

Lines changed: 253 additions & 32 deletions

File tree

gradlew

100644100755
File mode changed.

src/main/java/com/falsepattern/lib/internal/asm/transformers/ConfigOrderTransformer.java

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import com.falsepattern.lib.config.Config;
2626
import com.falsepattern.lib.internal.Tags;
2727
import com.falsepattern.lib.internal.impl.config.DeclOrderInternal;
28+
import com.falsepattern.lib.turboasm.BytePatternMatcher;
29+
import com.falsepattern.lib.turboasm.ClassHeaderMetadata;
2830
import com.falsepattern.lib.turboasm.ClassNodeHandle;
2931
import com.falsepattern.lib.turboasm.TurboClassTransformer;
3032
import lombok.val;
@@ -37,6 +39,8 @@ public class ConfigOrderTransformer implements TurboClassTransformer {
3739
private static final String DESC_CONFIG_IGNORE = Type.getDescriptor(Config.Ignore.class);
3840
private static final String DESC_ORDER = Type.getDescriptor(DeclOrderInternal.class);
3941

42+
final BytePatternMatcher configAnnotationMatcher = new BytePatternMatcher(DESC_CONFIG, BytePatternMatcher.Mode.Equals);
43+
4044
@Override
4145
public String name() {
4246
return "ConfigOrderTransformer";
@@ -49,17 +53,12 @@ public String owner() {
4953

5054
@Override
5155
public boolean shouldTransformClass(@NotNull String className, @NotNull ClassNodeHandle classNode) {
52-
val cn = classNode.getNode();
53-
if (cn == null)
56+
final ClassHeaderMetadata metadata = classNode.getOriginalMetadata();
57+
if (metadata == null) {
5458
return false;
55-
if (cn.visibleAnnotations != null) {
56-
for (val ann : cn.visibleAnnotations) {
57-
if (DESC_CONFIG.equals(ann.desc)) {
58-
return true;
59-
}
60-
}
6159
}
62-
return false;
60+
61+
return metadata.matchesBytes(configAnnotationMatcher);
6362
}
6463

6564
@Override
@@ -75,7 +74,9 @@ public boolean transformClass(@NotNull String className, @NotNull ClassNodeHandl
7574
|| (field.access & Opcodes.ACC_STATIC) == 0
7675
|| (field.access & Opcodes.ACC_FINAL) != 0) {
7776
continue;
78-
} else if (field.visibleAnnotations != null) {
77+
}
78+
79+
if (field.visibleAnnotations != null) {
7980
for (val ann : field.visibleAnnotations) {
8081
if (DESC_CONFIG_IGNORE.equals(ann.desc)) {
8182
continue outer;

src/main/java/com/falsepattern/lib/internal/asm/transformers/MixinPluginTransformer.java

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
package com.falsepattern.lib.internal.asm.transformers;
2424

2525
import com.falsepattern.lib.internal.Tags;
26+
import com.falsepattern.lib.turboasm.ClassHeaderMetadata;
2627
import com.falsepattern.lib.turboasm.ClassNodeHandle;
2728
import com.falsepattern.lib.turboasm.TurboClassTransformer;
2829
import lombok.val;
@@ -75,17 +76,11 @@ public boolean shouldTransformClass(@NotNull String className, @NotNull ClassNod
7576
if (!classNode.isPresent())
7677
return false;
7778

78-
if (classNode.isOriginal()) {
79-
val meta = classNode.getOriginalMetadata();
80-
if (meta != null && meta.interfacesCount == 0)
81-
return false;
82-
}
83-
84-
val cn = classNode.getNode();
85-
if (cn == null)
79+
ClassHeaderMetadata metadata = classNode.getOriginalMetadata();
80+
if (metadata == null)
8681
return false;
8782

88-
for (String i : cn.interfaces) {
83+
for (String i : metadata.binaryInterfaceNames()) {
8984
if (IMIXINPLUGIN_INTERNAL.equals(i) || IMIXINCONFIGPLUGIN_INTERNAL.equals(i)) {
9085
return true;
9186
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package com.falsepattern.lib.turboasm;
2+
3+
import java.nio.charset.StandardCharsets;
4+
import java.util.Arrays;
5+
6+
public class BytePatternMatcher {
7+
final Mode mode;
8+
// first byte -> matched patterns
9+
final byte[][][] byFirst = new byte[256][][];
10+
int minPatternLen = Integer.MAX_VALUE;
11+
12+
public enum Mode {
13+
/** Checks if the constant pool entry contains a pattern */
14+
Contains,
15+
/** Checks if the whole constant pool entry equals a pattern */
16+
Equals,
17+
/** Checks if the constant pool entry starts with a pattern */
18+
StartsWith
19+
}
20+
21+
public BytePatternMatcher(String strPattern, Mode mode) {
22+
this(new String[] {strPattern}, mode);
23+
}
24+
25+
public BytePatternMatcher(String[] strPatterns, Mode mode) {
26+
this.mode = mode;
27+
28+
final byte[][] patterns = new byte[strPatterns.length][];
29+
final int[] bucketSizes = new int[256];
30+
final int[] bucketIndices = new int[Math.min(strPatterns.length, 256)];
31+
int bucketsCount = 0;
32+
33+
for (int i = 0; i < strPatterns.length; i++) {
34+
final byte[] pattern = strPatterns[i].getBytes(StandardCharsets.UTF_8);
35+
patterns[i] = pattern;
36+
37+
if (pattern.length < minPatternLen) {
38+
minPatternLen = pattern.length;
39+
}
40+
41+
final int bucketIndex = pattern[0] & 0xFF;
42+
if (bucketSizes[bucketIndex]++ == 0) {
43+
bucketIndices[bucketsCount++] = bucketIndex;
44+
}
45+
}
46+
47+
// Ascending sorting by length
48+
Arrays.sort(patterns, (a, b) -> Integer.compare(a.length, b.length));
49+
50+
for (int i = 0; i < bucketsCount; i++) {
51+
final int bucketIndex = bucketIndices[i];
52+
byFirst[bucketIndex] = new byte[bucketSizes[bucketIndex]][];
53+
bucketSizes[bucketIndex] = 0; // reuse as write index
54+
}
55+
56+
for (final byte[] pattern : patterns) {
57+
final int bucketIndex = pattern[0] & 0xFF;
58+
byFirst[bucketIndex][bucketSizes[bucketIndex]++] = pattern;
59+
}
60+
}
61+
62+
public boolean matches(byte[] bytes, int start, int len) {
63+
if (len < minPatternLen) {
64+
return false;
65+
}
66+
67+
return switch (mode) {
68+
case Contains -> contains(bytes, start, len);
69+
case Equals -> equals(bytes, start, len);
70+
case StartsWith -> startsWith(bytes, start, len);
71+
};
72+
}
73+
74+
private boolean contains(byte[] bytes, int start, int len) {
75+
final int end = start + len;
76+
77+
for (int pos = start; pos <= end - minPatternLen; pos++) {
78+
final byte[][] patterns = byFirst[bytes[pos] & 0xFF];
79+
if (patterns == null) {
80+
continue;
81+
}
82+
83+
for (final byte[] pattern : patterns) {
84+
if (pattern.length > end - pos) {
85+
break;
86+
}
87+
88+
int k = pattern.length - 1;
89+
while (k > 0 && bytes[pos + k] == pattern[k]) k--;
90+
if (k == 0) return true;
91+
}
92+
}
93+
94+
return false;
95+
}
96+
97+
private boolean equals(byte[] bytes, int start, int len) {
98+
final byte[][] patterns = byFirst[bytes[start] & 0xFF];
99+
if (patterns == null) {
100+
return false;
101+
}
102+
103+
for (final byte[] pattern : patterns) {
104+
if (pattern.length < len) {
105+
continue;
106+
}
107+
if (pattern.length > len) {
108+
break;
109+
}
110+
111+
int k = pattern.length - 1;
112+
while (k > 0 && bytes[start + k] == pattern[k]) k--;
113+
if (k == 0) return true;
114+
}
115+
116+
return false;
117+
}
118+
119+
private boolean startsWith(byte[] bytes, int start, int len) {
120+
final byte[][] patterns = byFirst[bytes[start] & 0xFF];
121+
if (patterns == null) {
122+
return false;
123+
}
124+
125+
for (final byte[] pattern : patterns) {
126+
if (pattern.length > len) {
127+
break;
128+
}
129+
130+
int k = pattern.length - 1;
131+
while (k > 0 && bytes[start + k] == pattern[k]) k--;
132+
if (k == 0) return true;
133+
}
134+
135+
return false;
136+
}
137+
}

src/main/java/com/falsepattern/lib/turboasm/ClassHeaderMetadata.java

Lines changed: 71 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,17 @@
3030
import java.io.ByteArrayInputStream;
3131
import java.io.DataInputStream;
3232
import java.io.IOException;
33+
import java.util.ArrayList;
34+
import java.util.Arrays;
35+
import java.util.Collections;
36+
import java.util.List;
3337

3438
/**
3539
* Utilities for quickly processing class files without fully parsing them.
3640
*/
3741
public final class ClassHeaderMetadata implements FastClassAccessor {
3842

43+
public final byte[] classBytes;
3944
public final int minorVersion;
4045
public final int majorVersion;
4146
public final int constantPoolEntryCount;
@@ -44,13 +49,19 @@ public final class ClassHeaderMetadata implements FastClassAccessor {
4449
/** Type of each parsed constant pool entry, zero-indexed! */
4550
public final ConstantPoolEntryTypes @NotNull [] constantPoolEntryTypes;
4651

52+
/** Approximately only half of the entries are utf-8, so this array can be used to iterate over them more quickly */
53+
public final int @NotNull [] constantPoolUtf8EntryOffsets;
54+
4755
public final int constantPoolEndOffset;
4856
public final int accessFlags;
4957
public final int thisClassIndex;
5058
public final int superClassIndex;
5159
public final int interfacesCount;
60+
public final int @NotNull [] interfaceIndices;
5261
public final @NotNull String binaryThisName;
5362
public final @Nullable String binarySuperName;
63+
/** List is unmodifiable */
64+
public final @NotNull List<@NotNull String> binaryInterfaceNames;
5465

5566
/**
5667
* Attempts to parse a class header.
@@ -60,15 +71,17 @@ public ClassHeaderMetadata(byte @NotNull [] bytes) {
6071
if (!isValidClass(bytes)) {
6172
throw new IllegalArgumentException("Invalid class detected");
6273
}
74+
this.classBytes = bytes;
6375
this.minorVersion = u16(bytes, Offsets.minorVersionU16);
6476
this.majorVersion = u16(bytes, Offsets.majorVersionU16);
6577
this.constantPoolEntryCount = u16(bytes, Offsets.constantPoolCountU16);
6678
this.constantPoolEntryOffsets = new int[constantPoolEntryCount];
6779
this.constantPoolEntryTypes = new ConstantPoolEntryTypes[constantPoolEntryCount];
6880
// scan through CP entries
69-
final int cpOff;
7081
{
7182
int off = Offsets.constantPoolStart;
83+
final int[] utf8EntryOffsets = new int[constantPoolEntryCount];
84+
int utf8Entries = 0;
7285
for (int entry = 0; entry < constantPoolEntryCount - 1; entry++) {
7386
constantPoolEntryOffsets[entry] = off;
7487
ConstantPoolEntryTypes type = ConstantPoolEntryTypes.parse(bytes, off);
@@ -78,16 +91,19 @@ public ClassHeaderMetadata(byte @NotNull [] bytes) {
7891
entry++;
7992
constantPoolEntryOffsets[entry] = off;
8093
constantPoolEntryTypes[entry] = type;
94+
} else if (type == ConstantPoolEntryTypes.Utf8) {
95+
utf8EntryOffsets[utf8Entries++] = off;
8196
}
8297
off += type.byteLength(bytes, off);
8398
}
84-
cpOff = off;
85-
this.constantPoolEndOffset = cpOff;
99+
this.constantPoolEndOffset = off;
100+
this.constantPoolUtf8EntryOffsets = Arrays.copyOf(utf8EntryOffsets, utf8Entries);
86101
}
87-
this.accessFlags = u16(bytes, cpOff + Offsets.pastCpAccessFlagsU16);
88-
this.thisClassIndex = u16(bytes, cpOff + Offsets.pastCpThisClassU16);
89-
this.superClassIndex = u16(bytes, cpOff + Offsets.pastCpSuperClassU16);
90-
this.interfacesCount = u16(bytes, cpOff + Offsets.pastCpInterfacesCountU16);
102+
this.accessFlags = u16(bytes, this.constantPoolEndOffset + Offsets.pastCpAccessFlagsU16);
103+
this.thisClassIndex = u16(bytes, this.constantPoolEndOffset + Offsets.pastCpThisClassU16);
104+
this.superClassIndex = u16(bytes, this.constantPoolEndOffset + Offsets.pastCpSuperClassU16);
105+
this.interfacesCount = u16(bytes, this.constantPoolEndOffset + Offsets.pastCpInterfacesCountU16);
106+
91107
// Parse this&super names
92108
if (constantPoolEntryTypes[thisClassIndex - 1] != ConstantPoolEntryTypes.Class) {
93109
throw new IllegalArgumentException("This class index is not a class ref");
@@ -110,6 +126,27 @@ public ClassHeaderMetadata(byte @NotNull [] bytes) {
110126
}
111127
this.binarySuperName = modifiedUtf8(bytes, constantPoolEntryOffsets[superNameIndex - 1] + 1);
112128
}
129+
130+
// Parse interface names
131+
List<String> interfaceNames = new ArrayList<>(this.interfacesCount);
132+
this.interfaceIndices = new int[this.interfacesCount];
133+
for (int i = 0; i < this.interfacesCount; i++) {
134+
final int interfaceOffset = this.constantPoolEndOffset + Offsets.pastCpInterfacesList + i * 2;
135+
final int interfaceIndex = u16(bytes, interfaceOffset);
136+
if (constantPoolEntryTypes[interfaceIndex - 1] != ConstantPoolEntryTypes.Class) {
137+
throw new IllegalArgumentException("Interface " + i + " index is not a class ref");
138+
}
139+
final int interfaceNameIndex = u16(bytes, constantPoolEntryOffsets[interfaceIndex - 1] + 1);
140+
if (constantPoolEntryTypes[interfaceNameIndex - 1] != ConstantPoolEntryTypes.Utf8) {
141+
throw new IllegalArgumentException("Interface " + i + " index does not point to a UTF8 entry");
142+
}
143+
final String binaryInterfaceName =
144+
modifiedUtf8(bytes, constantPoolEntryOffsets[interfaceNameIndex - 1] + 1);
145+
146+
this.interfaceIndices[i] = interfaceIndex;
147+
interfaceNames.add(binaryInterfaceName);
148+
}
149+
this.binaryInterfaceNames = Collections.unmodifiableList(interfaceNames);
113150
}
114151

115152
/** Helpers to read big-endian values from class files. */
@@ -189,6 +226,8 @@ private Offsets() {}
189226
public static final int pastCpSuperClassU16 = pastCpThisClassU16 + 2;
190227
/** The value of the interfaces_count item gives the number of direct superinterfaces of this class or interface type */
191228
public static final int pastCpInterfacesCountU16 = pastCpSuperClassU16 + 2;
229+
230+
public static final int pastCpInterfacesList = pastCpInterfacesCountU16 + 2;
192231
}
193232

194233
public enum ConstantPoolEntryTypes {
@@ -284,11 +323,26 @@ public static int majorVersion(byte @NotNull [] classBytes) {
284323
}
285324

286325
/**
287-
* Searches for a sub"string" (byte array) in a longer byte array. Not efficient for long search strings.
288-
* @param classBytes The long byte string to search in.
289-
* @param substring The short substring to search for.
290-
* @return If the substring was found somewhere in the long string.
326+
* Searches for byte patterns in the constant pool.
327+
* @param matcher A configured byte matcher with patterns to search for.
328+
* @return {@code true} if there is a match for at least one constant pool entry.
291329
*/
330+
public boolean matchesBytes(final BytePatternMatcher matcher) {
331+
for (final int offset : constantPoolUtf8EntryOffsets) {
332+
// first byte is entry type, second and third bytes are length
333+
final int length = u16(classBytes, offset + 1);
334+
final int start = offset + 3;
335+
336+
if (matcher.matches(classBytes, start, length)) {
337+
return true;
338+
}
339+
}
340+
341+
return false;
342+
}
343+
344+
/** @deprecated This method is very slow, use {@link #matchesBytes} instead */
345+
@Deprecated
292346
public static boolean hasSubstring(final byte @Nullable [] classBytes, final byte @NotNull [] substring) {
293347
if (classBytes == null) {
294348
return false;
@@ -354,4 +408,9 @@ public boolean isEnum() {
354408
public @Nullable String binarySuperName() {
355409
return binarySuperName;
356410
}
357-
}
411+
412+
@Override
413+
public @NotNull List<@NotNull String> binaryInterfaceNames() {
414+
return binaryInterfaceNames;
415+
}
416+
}

0 commit comments

Comments
 (0)