3030import java .io .ByteArrayInputStream ;
3131import java .io .DataInputStream ;
3232import 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 */
3741public 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