use byte matcher to find Config annotation descriptor instead of ClassNode

This commit is contained in:
danyadev committed 2026-05-20 07:39:42 +03:00
1 parent 0a088c2f3a
commit 17619eedcc
3 files changed
+186 -24

No files matched your search

@@ -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;
@@ -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;
}
}
@@ -31,6 +31,7 @@ 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;
@@ -39,6 +40,7 @@ import java.util.List;
*/
public final class ClassHeaderMetadata implements FastClassAccessor {
public final byte[] classBytes;
public final int minorVersion;
public final int majorVersion;
public final int constantPoolEntryCount;
@@ -47,6 +49,9 @@ 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;
@@ -66,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);
@@ -84,18 +91,18 @@ 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.interfaceIndices = new int[this.interfacesCount];
List<String> interfaceNames = new ArrayList<>(this.interfacesCount);
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) {
@@ -121,8 +128,10 @@ public final class ClassHeaderMetadata implements FastClassAccessor {
}
// 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 = cpOff + Offsets.pastCpInterfacesList + i * 2;
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");
@@ -314,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;