Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified gradlew
100644 → 100755
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
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;
Expand All @@ -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";
Expand All @@ -49,17 +53,12 @@ public String owner() {

@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
Expand All @@ -75,7 +74,9 @@ public boolean transformClass(@NotNull String className, @NotNull ClassNodeHandl
|| (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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -75,17 +76,11 @@ public boolean shouldTransformClass(@NotNull String className, @NotNull ClassNod
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;
}
Expand Down
137 changes: 137 additions & 0 deletions src/main/java/com/falsepattern/lib/turboasm/BytePatternMatcher.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,17 @@
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;
Expand All @@ -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.
Expand All @@ -60,15 +71,17 @@ public ClassHeaderMetadata(byte @NotNull [] bytes) {
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);
Expand All @@ -78,16 +91,19 @@ public ClassHeaderMetadata(byte @NotNull [] bytes) {
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");
Expand All @@ -110,6 +126,27 @@ public ClassHeaderMetadata(byte @NotNull [] bytes) {
}
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. */
Expand Down Expand Up @@ -189,6 +226,8 @@ private Offsets() {}
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 {
Expand Down Expand Up @@ -284,11 +323,26 @@ public static int majorVersion(byte @NotNull [] classBytes) {
}

/**
* 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;
Expand Down Expand Up @@ -354,4 +408,9 @@ public boolean isEnum() {
public @Nullable String binarySuperName() {
return binarySuperName;
}
}

@Override
public @NotNull List<@NotNull String> binaryInterfaceNames() {
return binaryInterfaceNames;
}
}
Loading