mirror of
https://github.com/FalsePattern/FalsePatternLib.git
synced 2026-11-13 19:34:04 +00:00
Merge remote-tracking branch 'danyadev/optimize-transformers'
This commit is contained in:
6 files changed
+253
-32
No files matched your search
+11
-10
@@ -25,6 +25,8 @@ package com.falsepattern.lib.internal.asm.transformers;
|
||||
import com.falsepattern.lib.config.Config;
|
||||
import com.falsepattern.lib.internal.Tags;
|
||||
import com.falsepattern.lib.internal.impl.config.DeclOrderInternal;
|
||||
import com.falsepattern.lib.turboasm.BytePatternMatcher;
|
||||
import com.falsepattern.lib.turboasm.ClassHeaderMetadata;
|
||||
import com.falsepattern.lib.turboasm.ClassNodeHandle;
|
||||
import com.falsepattern.lib.turboasm.TurboClassTransformer;
|
||||
import lombok.val;
|
||||
@@ -37,6 +39,8 @@ public class ConfigOrderTransformer implements TurboClassTransformer {
|
||||
private static final String DESC_CONFIG_IGNORE = Type.getDescriptor(Config.Ignore.class);
|
||||
private static final String DESC_ORDER = Type.getDescriptor(DeclOrderInternal.class);
|
||||
|
||||
final BytePatternMatcher configAnnotationMatcher = new BytePatternMatcher(DESC_CONFIG, BytePatternMatcher.Mode.Equals);
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "ConfigOrderTransformer";
|
||||
@@ -49,17 +53,12 @@ public class ConfigOrderTransformer implements TurboClassTransformer {
|
||||
|
||||
@Override
|
||||
public boolean shouldTransformClass(@NotNull String className, @NotNull ClassNodeHandle classNode) {
|
||||
val cn = classNode.getNode();
|
||||
if (cn == null)
|
||||
final ClassHeaderMetadata metadata = classNode.getOriginalMetadata();
|
||||
if (metadata == null) {
|
||||
return false;
|
||||
if (cn.visibleAnnotations != null) {
|
||||
for (val ann : cn.visibleAnnotations) {
|
||||
if (DESC_CONFIG.equals(ann.desc)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
return metadata.matchesBytes(configAnnotationMatcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,7 +74,9 @@ public class ConfigOrderTransformer implements TurboClassTransformer {
|
||||
|| (field.access & Opcodes.ACC_STATIC) == 0
|
||||
|| (field.access & Opcodes.ACC_FINAL) != 0) {
|
||||
continue;
|
||||
} else if (field.visibleAnnotations != null) {
|
||||
}
|
||||
|
||||
if (field.visibleAnnotations != null) {
|
||||
for (val ann : field.visibleAnnotations) {
|
||||
if (DESC_CONFIG_IGNORE.equals(ann.desc)) {
|
||||
continue outer;
|
||||
|
||||
+4
-9
@@ -23,6 +23,7 @@
|
||||
package com.falsepattern.lib.internal.asm.transformers;
|
||||
|
||||
import com.falsepattern.lib.internal.Tags;
|
||||
import com.falsepattern.lib.turboasm.ClassHeaderMetadata;
|
||||
import com.falsepattern.lib.turboasm.ClassNodeHandle;
|
||||
import com.falsepattern.lib.turboasm.TurboClassTransformer;
|
||||
import lombok.val;
|
||||
@@ -75,17 +76,11 @@ public class MixinPluginTransformer implements TurboClassTransformer {
|
||||
if (!classNode.isPresent())
|
||||
return false;
|
||||
|
||||
if (classNode.isOriginal()) {
|
||||
val meta = classNode.getOriginalMetadata();
|
||||
if (meta != null && meta.interfacesCount == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
val cn = classNode.getNode();
|
||||
if (cn == null)
|
||||
ClassHeaderMetadata metadata = classNode.getOriginalMetadata();
|
||||
if (metadata == null)
|
||||
return false;
|
||||
|
||||
for (String i : cn.interfaces) {
|
||||
for (String i : metadata.binaryInterfaceNames()) {
|
||||
if (IMIXINPLUGIN_INTERNAL.equals(i) || IMIXINCONFIGPLUGIN_INTERNAL.equals(i)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
package com.falsepattern.lib.turboasm;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class BytePatternMatcher {
|
||||
final Mode mode;
|
||||
// first byte -> matched patterns
|
||||
final byte[][][] byFirst = new byte[256][][];
|
||||
int minPatternLen = Integer.MAX_VALUE;
|
||||
|
||||
public enum Mode {
|
||||
/** Checks if the constant pool entry contains a pattern */
|
||||
Contains,
|
||||
/** Checks if the whole constant pool entry equals a pattern */
|
||||
Equals,
|
||||
/** Checks if the constant pool entry starts with a pattern */
|
||||
StartsWith
|
||||
}
|
||||
|
||||
public BytePatternMatcher(String strPattern, Mode mode) {
|
||||
this(new String[] {strPattern}, mode);
|
||||
}
|
||||
|
||||
public BytePatternMatcher(String[] strPatterns, Mode mode) {
|
||||
this.mode = mode;
|
||||
|
||||
final byte[][] patterns = new byte[strPatterns.length][];
|
||||
final int[] bucketSizes = new int[256];
|
||||
final int[] bucketIndices = new int[Math.min(strPatterns.length, 256)];
|
||||
int bucketsCount = 0;
|
||||
|
||||
for (int i = 0; i < strPatterns.length; i++) {
|
||||
final byte[] pattern = strPatterns[i].getBytes(StandardCharsets.UTF_8);
|
||||
patterns[i] = pattern;
|
||||
|
||||
if (pattern.length < minPatternLen) {
|
||||
minPatternLen = pattern.length;
|
||||
}
|
||||
|
||||
final int bucketIndex = pattern[0] & 0xFF;
|
||||
if (bucketSizes[bucketIndex]++ == 0) {
|
||||
bucketIndices[bucketsCount++] = bucketIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// Ascending sorting by length
|
||||
Arrays.sort(patterns, (a, b) -> Integer.compare(a.length, b.length));
|
||||
|
||||
for (int i = 0; i < bucketsCount; i++) {
|
||||
final int bucketIndex = bucketIndices[i];
|
||||
byFirst[bucketIndex] = new byte[bucketSizes[bucketIndex]][];
|
||||
bucketSizes[bucketIndex] = 0; // reuse as write index
|
||||
}
|
||||
|
||||
for (final byte[] pattern : patterns) {
|
||||
final int bucketIndex = pattern[0] & 0xFF;
|
||||
byFirst[bucketIndex][bucketSizes[bucketIndex]++] = pattern;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean matches(byte[] bytes, int start, int len) {
|
||||
if (len < minPatternLen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return switch (mode) {
|
||||
case Contains -> contains(bytes, start, len);
|
||||
case Equals -> equals(bytes, start, len);
|
||||
case StartsWith -> startsWith(bytes, start, len);
|
||||
};
|
||||
}
|
||||
|
||||
private boolean contains(byte[] bytes, int start, int len) {
|
||||
final int end = start + len;
|
||||
|
||||
for (int pos = start; pos <= end - minPatternLen; pos++) {
|
||||
final byte[][] patterns = byFirst[bytes[pos] & 0xFF];
|
||||
if (patterns == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (final byte[] pattern : patterns) {
|
||||
if (pattern.length > end - pos) {
|
||||
break;
|
||||
}
|
||||
|
||||
int k = pattern.length - 1;
|
||||
while (k > 0 && bytes[pos + k] == pattern[k]) k--;
|
||||
if (k == 0) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean equals(byte[] bytes, int start, int len) {
|
||||
final byte[][] patterns = byFirst[bytes[start] & 0xFF];
|
||||
if (patterns == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (final byte[] pattern : patterns) {
|
||||
if (pattern.length < len) {
|
||||
continue;
|
||||
}
|
||||
if (pattern.length > len) {
|
||||
break;
|
||||
}
|
||||
|
||||
int k = pattern.length - 1;
|
||||
while (k > 0 && bytes[start + k] == pattern[k]) k--;
|
||||
if (k == 0) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean startsWith(byte[] bytes, int start, int len) {
|
||||
final byte[][] patterns = byFirst[bytes[start] & 0xFF];
|
||||
if (patterns == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (final byte[] pattern : patterns) {
|
||||
if (pattern.length > len) {
|
||||
break;
|
||||
}
|
||||
|
||||
int k = pattern.length - 1;
|
||||
while (k > 0 && bytes[start + k] == pattern[k]) k--;
|
||||
if (k == 0) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -30,12 +30,17 @@ import org.objectweb.asm.Opcodes;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utilities for quickly processing class files without fully parsing them.
|
||||
*/
|
||||
public final class ClassHeaderMetadata implements FastClassAccessor {
|
||||
|
||||
public final byte[] classBytes;
|
||||
public final int minorVersion;
|
||||
public final int majorVersion;
|
||||
public final int constantPoolEntryCount;
|
||||
@@ -44,13 +49,19 @@ public final class ClassHeaderMetadata implements FastClassAccessor {
|
||||
/** Type of each parsed constant pool entry, zero-indexed! */
|
||||
public final ConstantPoolEntryTypes @NotNull [] constantPoolEntryTypes;
|
||||
|
||||
/** Approximately only half of the entries are utf-8, so this array can be used to iterate over them more quickly */
|
||||
public final int @NotNull [] constantPoolUtf8EntryOffsets;
|
||||
|
||||
public final int constantPoolEndOffset;
|
||||
public final int accessFlags;
|
||||
public final int thisClassIndex;
|
||||
public final int superClassIndex;
|
||||
public final int interfacesCount;
|
||||
public final int @NotNull [] interfaceIndices;
|
||||
public final @NotNull String binaryThisName;
|
||||
public final @Nullable String binarySuperName;
|
||||
/** List is unmodifiable */
|
||||
public final @NotNull List<@NotNull String> binaryInterfaceNames;
|
||||
|
||||
/**
|
||||
* Attempts to parse a class header.
|
||||
@@ -60,15 +71,17 @@ public final class ClassHeaderMetadata implements FastClassAccessor {
|
||||
if (!isValidClass(bytes)) {
|
||||
throw new IllegalArgumentException("Invalid class detected");
|
||||
}
|
||||
this.classBytes = bytes;
|
||||
this.minorVersion = u16(bytes, Offsets.minorVersionU16);
|
||||
this.majorVersion = u16(bytes, Offsets.majorVersionU16);
|
||||
this.constantPoolEntryCount = u16(bytes, Offsets.constantPoolCountU16);
|
||||
this.constantPoolEntryOffsets = new int[constantPoolEntryCount];
|
||||
this.constantPoolEntryTypes = new ConstantPoolEntryTypes[constantPoolEntryCount];
|
||||
// scan through CP entries
|
||||
final int cpOff;
|
||||
{
|
||||
int off = Offsets.constantPoolStart;
|
||||
final int[] utf8EntryOffsets = new int[constantPoolEntryCount];
|
||||
int utf8Entries = 0;
|
||||
for (int entry = 0; entry < constantPoolEntryCount - 1; entry++) {
|
||||
constantPoolEntryOffsets[entry] = off;
|
||||
ConstantPoolEntryTypes type = ConstantPoolEntryTypes.parse(bytes, off);
|
||||
@@ -78,16 +91,19 @@ public final class ClassHeaderMetadata implements FastClassAccessor {
|
||||
entry++;
|
||||
constantPoolEntryOffsets[entry] = off;
|
||||
constantPoolEntryTypes[entry] = type;
|
||||
} else if (type == ConstantPoolEntryTypes.Utf8) {
|
||||
utf8EntryOffsets[utf8Entries++] = off;
|
||||
}
|
||||
off += type.byteLength(bytes, off);
|
||||
}
|
||||
cpOff = off;
|
||||
this.constantPoolEndOffset = cpOff;
|
||||
this.constantPoolEndOffset = off;
|
||||
this.constantPoolUtf8EntryOffsets = Arrays.copyOf(utf8EntryOffsets, utf8Entries);
|
||||
}
|
||||
this.accessFlags = u16(bytes, cpOff + Offsets.pastCpAccessFlagsU16);
|
||||
this.thisClassIndex = u16(bytes, cpOff + Offsets.pastCpThisClassU16);
|
||||
this.superClassIndex = u16(bytes, cpOff + Offsets.pastCpSuperClassU16);
|
||||
this.interfacesCount = u16(bytes, cpOff + Offsets.pastCpInterfacesCountU16);
|
||||
this.accessFlags = u16(bytes, this.constantPoolEndOffset + Offsets.pastCpAccessFlagsU16);
|
||||
this.thisClassIndex = u16(bytes, this.constantPoolEndOffset + Offsets.pastCpThisClassU16);
|
||||
this.superClassIndex = u16(bytes, this.constantPoolEndOffset + Offsets.pastCpSuperClassU16);
|
||||
this.interfacesCount = u16(bytes, this.constantPoolEndOffset + Offsets.pastCpInterfacesCountU16);
|
||||
|
||||
// Parse this&super names
|
||||
if (constantPoolEntryTypes[thisClassIndex - 1] != ConstantPoolEntryTypes.Class) {
|
||||
throw new IllegalArgumentException("This class index is not a class ref");
|
||||
@@ -110,6 +126,27 @@ public final class ClassHeaderMetadata implements FastClassAccessor {
|
||||
}
|
||||
this.binarySuperName = modifiedUtf8(bytes, constantPoolEntryOffsets[superNameIndex - 1] + 1);
|
||||
}
|
||||
|
||||
// Parse interface names
|
||||
List<String> interfaceNames = new ArrayList<>(this.interfacesCount);
|
||||
this.interfaceIndices = new int[this.interfacesCount];
|
||||
for (int i = 0; i < this.interfacesCount; i++) {
|
||||
final int interfaceOffset = this.constantPoolEndOffset + Offsets.pastCpInterfacesList + i * 2;
|
||||
final int interfaceIndex = u16(bytes, interfaceOffset);
|
||||
if (constantPoolEntryTypes[interfaceIndex - 1] != ConstantPoolEntryTypes.Class) {
|
||||
throw new IllegalArgumentException("Interface " + i + " index is not a class ref");
|
||||
}
|
||||
final int interfaceNameIndex = u16(bytes, constantPoolEntryOffsets[interfaceIndex - 1] + 1);
|
||||
if (constantPoolEntryTypes[interfaceNameIndex - 1] != ConstantPoolEntryTypes.Utf8) {
|
||||
throw new IllegalArgumentException("Interface " + i + " index does not point to a UTF8 entry");
|
||||
}
|
||||
final String binaryInterfaceName =
|
||||
modifiedUtf8(bytes, constantPoolEntryOffsets[interfaceNameIndex - 1] + 1);
|
||||
|
||||
this.interfaceIndices[i] = interfaceIndex;
|
||||
interfaceNames.add(binaryInterfaceName);
|
||||
}
|
||||
this.binaryInterfaceNames = Collections.unmodifiableList(interfaceNames);
|
||||
}
|
||||
|
||||
/** Helpers to read big-endian values from class files. */
|
||||
@@ -189,6 +226,8 @@ public final class ClassHeaderMetadata implements FastClassAccessor {
|
||||
public static final int pastCpSuperClassU16 = pastCpThisClassU16 + 2;
|
||||
/** The value of the interfaces_count item gives the number of direct superinterfaces of this class or interface type */
|
||||
public static final int pastCpInterfacesCountU16 = pastCpSuperClassU16 + 2;
|
||||
|
||||
public static final int pastCpInterfacesList = pastCpInterfacesCountU16 + 2;
|
||||
}
|
||||
|
||||
public enum ConstantPoolEntryTypes {
|
||||
@@ -284,11 +323,26 @@ public final class ClassHeaderMetadata implements FastClassAccessor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a sub"string" (byte array) in a longer byte array. Not efficient for long search strings.
|
||||
* @param classBytes The long byte string to search in.
|
||||
* @param substring The short substring to search for.
|
||||
* @return If the substring was found somewhere in the long string.
|
||||
* Searches for byte patterns in the constant pool.
|
||||
* @param matcher A configured byte matcher with patterns to search for.
|
||||
* @return {@code true} if there is a match for at least one constant pool entry.
|
||||
*/
|
||||
public boolean matchesBytes(final BytePatternMatcher matcher) {
|
||||
for (final int offset : constantPoolUtf8EntryOffsets) {
|
||||
// first byte is entry type, second and third bytes are length
|
||||
final int length = u16(classBytes, offset + 1);
|
||||
final int start = offset + 3;
|
||||
|
||||
if (matcher.matches(classBytes, start, length)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @deprecated This method is very slow, use {@link #matchesBytes} instead */
|
||||
@Deprecated
|
||||
public static boolean hasSubstring(final byte @Nullable [] classBytes, final byte @NotNull [] substring) {
|
||||
if (classBytes == null) {
|
||||
return false;
|
||||
@@ -354,4 +408,9 @@ public final class ClassHeaderMetadata implements FastClassAccessor {
|
||||
public @Nullable String binarySuperName() {
|
||||
return binarySuperName;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<@NotNull String> binaryInterfaceNames() {
|
||||
return binaryInterfaceNames;
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,9 @@ import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** An accessor to metadata about a class that is quickly accessible without fully parsing one. */
|
||||
public interface FastClassAccessor {
|
||||
@@ -51,6 +54,9 @@ public interface FastClassAccessor {
|
||||
/** Binary (slash-separated packages) name of the super-class, null for the Object class */
|
||||
@Nullable
|
||||
String binarySuperName();
|
||||
/** Binary (slash-separated packages) names of the implemented interfaces, list is unmodifiable */
|
||||
@NotNull
|
||||
List<@NotNull String> binaryInterfaceNames();
|
||||
|
||||
static OfLoaded ofLoaded(Class<?> loadedClass) {
|
||||
return new OfLoaded(loadedClass);
|
||||
@@ -111,10 +117,18 @@ public interface FastClassAccessor {
|
||||
public @Nullable String binarySuperName() {
|
||||
return handle.superName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<@NotNull String> binaryInterfaceNames() {
|
||||
return handle.interfaces == null
|
||||
? Collections.emptyList()
|
||||
: Collections.unmodifiableList(handle.interfaces);
|
||||
}
|
||||
}
|
||||
|
||||
final class OfLoaded implements FastClassAccessor {
|
||||
public final Class<?> handle;
|
||||
private @Nullable List<@NotNull String> interfacesCache = null;
|
||||
|
||||
private OfLoaded(Class<?> handle) {
|
||||
this.handle = handle;
|
||||
@@ -165,5 +179,20 @@ public interface FastClassAccessor {
|
||||
final Class<?> superclass = handle.getSuperclass();
|
||||
return superclass == null ? null : superclass.getName().replace('.', '/');
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<@NotNull String> binaryInterfaceNames() {
|
||||
if (interfacesCache != null) {
|
||||
return interfacesCache;
|
||||
}
|
||||
|
||||
Class<?>[] interfaces = handle.getInterfaces();
|
||||
List<String> binaryInterfaceNames = new ArrayList<>(interfaces.length);
|
||||
for (Class<?> iface : interfaces) {
|
||||
binaryInterfaceNames.add(iface.getName().replace('.', '/'));
|
||||
}
|
||||
interfacesCache = Collections.unmodifiableList(binaryInterfaceNames);
|
||||
return interfacesCache;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user