From bb10aee9f6a158a203d4afcb4d61234660a5cfda Mon Sep 17 00:00:00 2001 From: Stuart McCulloch Date: Fri, 30 Oct 2026 12:04:12 +0000 Subject: [PATCH] Adjust ClassFile super-name check to be more robust --- .../instrument/classmatch/ClassFile.java | 18 +++++---- .../instrument/classmatch/ClassHeader.java | 2 +- .../instrument/classmatch/ClassFileTest.java | 40 +++++++++++++++++++ 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/class-match/src/main/java/datadog/instrument/classmatch/ClassFile.java b/class-match/src/main/java/datadog/instrument/classmatch/ClassFile.java index eb915258..717de20d 100644 --- a/class-match/src/main/java/datadog/instrument/classmatch/ClassFile.java +++ b/class-match/src/main/java/datadog/instrument/classmatch/ClassFile.java @@ -35,7 +35,6 @@ public final class ClassFile { private static final String[] NO_ANNOTATIONS = {}; private static final int ACC_INTERFACE = 0x0200; - private static final int ACC_MODULE = 0x8000; private static final byte[] JAVA_LANG_OBJECT_BYTES = JAVA_LANG_OBJECT.getBytes(US_ASCII); private static final byte[] CONSTRUCTOR_BYTES = CONSTRUCTOR.getBytes(US_ASCII); @@ -213,14 +212,17 @@ private static ClassHeader parse(byte[] bytecode, int offset, boolean onlyHeader String superName; if ((access & ACC_INTERFACE) != 0) { superName = JAVA_LANG_OBJECT; - } else if (access != ACC_MODULE) { - int superNameOffset = cp[cp[u2(bytecode, cursor)]]; - superName = - utfEquals(bytecode, superNameOffset, JAVA_LANG_OBJECT_BYTES) - ? JAVA_LANG_OBJECT - : utf(bytecode, superNameOffset); } else { - superName = null; + // java.lang.Object and modules have a zero super_class index (and therefore offset) + int superNameOffset = cp[cp[u2(bytecode, cursor)]]; + if (superNameOffset != 0) { + superName = + utfEquals(bytecode, superNameOffset, JAVA_LANG_OBJECT_BYTES) + ? JAVA_LANG_OBJECT + : utf(bytecode, superNameOffset); + } else { + superName = null; + } } cursor += 2; diff --git a/class-match/src/main/java/datadog/instrument/classmatch/ClassHeader.java b/class-match/src/main/java/datadog/instrument/classmatch/ClassHeader.java index 38213284..f259f40f 100644 --- a/class-match/src/main/java/datadog/instrument/classmatch/ClassHeader.java +++ b/class-match/src/main/java/datadog/instrument/classmatch/ClassHeader.java @@ -22,7 +22,7 @@ public class ClassHeader { /** Internal name of this class. */ public final String className; - /** Internal name of the super-class declared by this class; {@code null} for modules. */ + /** Internal name of the super-class; {@code null} for {@code java.lang.Object} and modules. */ @Nullable public final String superName; /** Internal names of the interfaces declared by this class. */ diff --git a/class-match/src/test/java/datadog/instrument/classmatch/ClassFileTest.java b/class-match/src/test/java/datadog/instrument/classmatch/ClassFileTest.java index d51c1153..25a4ec90 100644 --- a/class-match/src/test/java/datadog/instrument/classmatch/ClassFileTest.java +++ b/class-match/src/test/java/datadog/instrument/classmatch/ClassFileTest.java @@ -3,8 +3,11 @@ import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.fail; +import datadog.instrument.utils.JVM; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -16,6 +19,8 @@ import java.util.function.Consumer; import java.util.jar.JarFile; import org.junit.jupiter.api.Test; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Opcodes; class ClassFileTest { @@ -48,6 +53,41 @@ void header() { testParsing("asm-test.jar", ClassFile::header); } + @Test + void objectParsing() throws IOException { + try (InputStream in = Object.class.getResourceAsStream("Object.class")) { + assertNotNull(in); + byte[] bytecode = JVM.readAllBytes(in); + for (ClassHeader header : + new ClassHeader[] {ClassFile.header(bytecode), ClassFile.outline(bytecode)}) { + assertEquals("java/lang/Object", header.className); + assertNull(header.superName); + assertArrayEquals(new String[0], header.interfaces); + } + } + } + + @Test + void moduleParsing() throws IOException { + byte[] bytecode; + try (JarFile sample = new JarFile("build/sampleBytecode/asm-test.jar"); + InputStream in = sample.getInputStream(sample.getEntry("jdk9/module-info.class"))) { + bytecode = JVM.readAllBytes(in); + } + // also check a module with extra access flags, such as ACC_SYNTHETIC + byte[] syntheticBytecode = bytecode.clone(); + int accessOffset = new ClassReader(syntheticBytecode).header; + syntheticBytecode[accessOffset] |= (byte) (Opcodes.ACC_SYNTHETIC >> 8); + for (byte[] moduleBytecode : new byte[][] {bytecode, syntheticBytecode}) { + for (ClassHeader header : + new ClassHeader[] {ClassFile.header(moduleBytecode), ClassFile.outline(moduleBytecode)}) { + assertEquals("module-info", header.className); + assertNull(header.superName); + assertArrayEquals(new String[0], header.interfaces); + } + } + } + @Test void unicodeHeader() { ClassHeader header = ClassFile.header(sampleUnicodeClass);