From 8cba3844c63415f84703981084622e3f128cce98 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 01:40:34 +0000 Subject: [PATCH 1/3] Initial plan From bc62bb2bd4e8e52d6823f7e1ca6ff2514fc4c908 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 01:59:24 +0000 Subject: [PATCH 2/3] Migrate from ASM to Java 25 Class-File API, update Java version to 25 Co-authored-by: Goooler <10363352+Goooler@users.noreply.github.com> --- .github/workflows/ci.yml | 4 +- pom.xml | 48 +--- .../java/org/vafer/jdependency/Clazzpath.java | 11 +- .../asm/DependenciesClassAdapter.java | 232 ++++++++---------- .../jdependency/utils/DependencyUtils.java | 3 +- .../jdependency/ClazzpathUnitTestCase.java | 4 +- 6 files changed, 110 insertions(+), 192 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb5f97de..d047b933 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,8 +11,8 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest] - # Always test on the latest version and LTS. - java: [8, 11, 17, 21, 25] + # Requires Java 25 for the Class-File API (java.lang.classfile) + java: [25] runs-on: ${{ matrix.os }} steps: - name: Checkout diff --git a/pom.xml b/pom.xml index 7834774e..6290c83d 100644 --- a/pom.xml +++ b/pom.xml @@ -7,13 +7,12 @@ UTF-8 UTF-8 - 8 - 8 - 8 + 25 + 25 + 25 3.9.6 ${project.build.directory}/test-working-directory - 9.9.1 4.0.0 org.vafer @@ -79,31 +78,6 @@ commons-io 2.21.0 - - org.ow2.asm - asm - ${asm.version} - - - org.ow2.asm - asm-analysis - ${asm.version} - - - org.ow2.asm - asm-commons - ${asm.version} - - - org.ow2.asm - asm-util - ${asm.version} - - - org.ow2.asm - asm-tree - ${asm.version} - @@ -250,7 +224,7 @@ shade - true + false *:* @@ -264,11 +238,6 @@ commons-io:commons-io - org.ow2.asm:asm - org.ow2.asm:asm-analysis - org.ow2.asm:asm-commons - org.ow2.asm:asm-util - org.ow2.asm:asm-tree @@ -276,15 +245,6 @@ org.apache.commons org.vafer.jdeb.shaded.commons - - org.ow2.asm - org.vafer.jdeb.shaded.ow2.asm - - - org.objectweb.asm - org.vafer.jdeb.shaded.objectweb.asm - diff --git a/src/main/java/org/vafer/jdependency/Clazzpath.java b/src/main/java/org/vafer/jdependency/Clazzpath.java index 29310f1b..b09db71c 100644 --- a/src/main/java/org/vafer/jdependency/Clazzpath.java +++ b/src/main/java/org/vafer/jdependency/Clazzpath.java @@ -31,8 +31,6 @@ import java.util.stream.Stream; import java.util.zip.ZipEntry; -import org.apache.commons.io.input.MessageDigestInputStream; -import org.objectweb.asm.ClassReader; import static org.apache.commons.io.FilenameUtils.normalize; import static org.apache.commons.io.FilenameUtils.separatorsToUnix; @@ -165,16 +163,15 @@ private ClazzpathUnit addClazzpathUnit( final Iterable resources, fina // extract dependencies of clazz InputStream inputStream = resource.getInputStream(); try { - final MessageDigest digest = MessageDigest.getInstance("SHA-256"); - final MessageDigestInputStream calculatingInputStream = - MessageDigestInputStream.builder().setInputStream(inputStream).setMessageDigest(digest).get(); + final byte[] classBytes = inputStream.readAllBytes(); + final MessageDigest digest = MessageDigest.getInstance("SHA-256"); if (versions) { - inputStream = calculatingInputStream; + digest.update(classBytes); } final DependenciesClassAdapter v = new DependenciesClassAdapter(); - new ClassReader(inputStream).accept(v, ClassReader.EXPAND_FRAMES | ClassReader.SKIP_DEBUG); + v.accept(classBytes); // get or create clazz final String clazzName = resource.name; diff --git a/src/main/java/org/vafer/jdependency/asm/DependenciesClassAdapter.java b/src/main/java/org/vafer/jdependency/asm/DependenciesClassAdapter.java index 4085716f..5fe128c9 100644 --- a/src/main/java/org/vafer/jdependency/asm/DependenciesClassAdapter.java +++ b/src/main/java/org/vafer/jdependency/asm/DependenciesClassAdapter.java @@ -15,166 +15,128 @@ */ package org.vafer.jdependency.asm; +import java.lang.classfile.Attributes; +import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassModel; +import java.lang.classfile.FieldModel; +import java.lang.classfile.MethodModel; +import java.lang.classfile.constantpool.ClassEntry; +import java.lang.classfile.constantpool.PoolEntry; +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; import java.util.HashSet; import java.util.Set; - -import org.objectweb.asm.AnnotationVisitor; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.FieldVisitor; -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.ModuleVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.RecordComponentVisitor; -import org.objectweb.asm.TypePath; -import org.objectweb.asm.commons.ClassRemapper; -import org.objectweb.asm.commons.Remapper; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * internal - do not use */ +public final class DependenciesClassAdapter { -public final class DependenciesClassAdapter extends ClassRemapper { + private static final Pattern SIGNATURE_CLASS_PATTERN = + Pattern.compile("L([a-zA-Z0-9_$]+(?:/[a-zA-Z0-9_$]+)*)"); - private static final int OPCODES = Opcodes.ASM9; + private final Set classes = new HashSet<>(); - private static final EmptyVisitor ev = new EmptyVisitor(); + public void accept(byte[] classBytes) { + ClassModel cm = ClassFile.of().parse(classBytes); - public DependenciesClassAdapter() { - super(ev, new CollectingRemapper()); - } + for (PoolEntry pe : cm.constantPool()) { + if (pe instanceof ClassEntry ce) { + collectFromInternalName(ce.asInternalName()); + } + } - public Set getDependencies() { - return ((CollectingRemapper) super.remapper).classes; - } + for (FieldModel fm : cm.fields()) { + collectFromClassDesc(fm.fieldTypeSymbol()); + fm.findAttribute(Attributes.signature()).ifPresent(sa -> + collectFromSignature(sa.signature().stringValue())); + fm.findAttribute(Attributes.runtimeVisibleAnnotations()).ifPresent(a -> + a.annotations().forEach(ann -> collectFromClassDesc(ann.classSymbol()))); + fm.findAttribute(Attributes.runtimeInvisibleAnnotations()).ifPresent(a -> + a.annotations().forEach(ann -> collectFromClassDesc(ann.classSymbol()))); + } - private static class CollectingRemapper extends Remapper { + for (MethodModel mm : cm.methods()) { + collectFromMethodTypeDesc(mm.methodTypeSymbol()); + mm.findAttribute(Attributes.exceptions()).ifPresent(ea -> + ea.exceptions().forEach(ce -> collectFromInternalName(ce.asInternalName()))); + mm.findAttribute(Attributes.signature()).ifPresent(sa -> + collectFromSignature(sa.signature().stringValue())); + mm.findAttribute(Attributes.runtimeVisibleAnnotations()).ifPresent(a -> + a.annotations().forEach(ann -> collectFromClassDesc(ann.classSymbol()))); + mm.findAttribute(Attributes.runtimeInvisibleAnnotations()).ifPresent(a -> + a.annotations().forEach(ann -> collectFromClassDesc(ann.classSymbol()))); + mm.findAttribute(Attributes.runtimeVisibleParameterAnnotations()).ifPresent(a -> + a.parameterAnnotations().forEach(list -> + list.forEach(ann -> collectFromClassDesc(ann.classSymbol())))); + mm.findAttribute(Attributes.runtimeInvisibleParameterAnnotations()).ifPresent(a -> + a.parameterAnnotations().forEach(list -> + list.forEach(ann -> collectFromClassDesc(ann.classSymbol())))); + } - final Set classes = new HashSet(); + cm.findAttribute(Attributes.signature()).ifPresent(sa -> + collectFromSignature(sa.signature().stringValue())); + cm.findAttribute(Attributes.runtimeVisibleAnnotations()).ifPresent(a -> + a.annotations().forEach(ann -> collectFromClassDesc(ann.classSymbol()))); + cm.findAttribute(Attributes.runtimeInvisibleAnnotations()).ifPresent(a -> + a.annotations().forEach(ann -> collectFromClassDesc(ann.classSymbol()))); + } - public String map(String pClassName) { - classes.add(pClassName.replace('/', '.')); - return pClassName; + private void collectFromInternalName(String internalName) { + if (internalName.startsWith("[")) { + collectFromDescriptor(internalName); + } else { + classes.add(internalName.replace('/', '.')); } } - static class EmptyVisitor extends ClassVisitor { - - private static final AnnotationVisitor av = new AnnotationVisitor(OPCODES) { - @Override - public AnnotationVisitor visitAnnotation(String name, String desc) { - return this; - } - - @Override - public AnnotationVisitor visitArray(String name) { - return this; - } - }; - - private static final MethodVisitor mv = new MethodVisitor(OPCODES) { - @Override - public AnnotationVisitor visitAnnotationDefault() { - return av; - } - - @Override - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - return av; - } - - @Override - public AnnotationVisitor visitParameterAnnotation( - int parameter, String desc, boolean visible) { - return av; - } - - @Override - public AnnotationVisitor visitInsnAnnotation( int typeRef, TypePath typePath, String descriptor, - boolean visible ) { - return av; - } - - @Override - public AnnotationVisitor visitLocalVariableAnnotation( int typeRef, TypePath typePath, Label[] start, - Label[] end, int[] index, String descriptor, - boolean visible ) { - return av; - } - - @Override - public AnnotationVisitor visitTryCatchAnnotation( int typeRef, TypePath typePath, String descriptor, - boolean visible ) { - return av; - } - - @Override - public AnnotationVisitor visitTypeAnnotation( int typeRef, TypePath typePath, String descriptor, - boolean visible ) { - return av; - } - }; - - private static final FieldVisitor fieldVisitor = new FieldVisitor(OPCODES) { - @Override - public AnnotationVisitor visitAnnotation( String desc, boolean visible ) { - return av; - } - @Override - public AnnotationVisitor visitTypeAnnotation( int typeRef, TypePath typePath, String descriptor, - boolean visible ) { - return av; - } - }; - - private static final ModuleVisitor moduleVisitor = new ModuleVisitor(OPCODES) { - }; - - private static final RecordComponentVisitor recordComponentVisitor = new RecordComponentVisitor(OPCODES) { - @Override - public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { - return av; + private void collectFromDescriptor(String descriptor) { + int i = 0; + while (i < descriptor.length()) { + char c = descriptor.charAt(i); + if (c == 'L') { + int end = descriptor.indexOf(';', i + 1); + if (end != -1) { + classes.add(descriptor.substring(i + 1, end).replace('/', '.')); + i = end + 1; + } else { + i++; + } + } else { + i++; } - - @Override - public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { - return av; - } - }; - - public EmptyVisitor() { - super(OPCODES); - } - - @Override - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - return av; - } - - @Override - public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { - return fieldVisitor; } + } - @Override - public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - return mv; + private void collectFromClassDesc(ClassDesc desc) { + if (desc.isArray()) { + collectFromClassDesc(desc.componentType()); + } else if (!desc.isPrimitive()) { + String descriptor = desc.descriptorString(); + if (descriptor.startsWith("L") && descriptor.endsWith(";")) { + classes.add(descriptor.substring(1, descriptor.length() - 1).replace('/', '.')); + } } + } - @Override - public AnnotationVisitor visitTypeAnnotation( int typeRef, TypePath typePath, String descriptor, - boolean visible ) { - return av; + private void collectFromMethodTypeDesc(MethodTypeDesc desc) { + for (ClassDesc param : desc.parameterList()) { + collectFromClassDesc(param); } + collectFromClassDesc(desc.returnType()); + } - @Override - public ModuleVisitor visitModule(String name, int access, String version) { - return moduleVisitor; + private void collectFromSignature(String signature) { + if (signature == null) return; + Matcher m = SIGNATURE_CLASS_PATTERN.matcher(signature); + while (m.find()) { + classes.add(m.group(1).replace('/', '.')); } + } - @Override - public RecordComponentVisitor visitRecordComponent(String name, String descriptor, String signature) { - return recordComponentVisitor; - } + public Set getDependencies() { + return classes; } } diff --git a/src/main/java/org/vafer/jdependency/utils/DependencyUtils.java b/src/main/java/org/vafer/jdependency/utils/DependencyUtils.java index efc7b912..97bf8784 100644 --- a/src/main/java/org/vafer/jdependency/utils/DependencyUtils.java +++ b/src/main/java/org/vafer/jdependency/utils/DependencyUtils.java @@ -19,7 +19,6 @@ import java.io.InputStream; import java.util.Set; -import org.objectweb.asm.ClassReader; import org.vafer.jdependency.asm.DependenciesClassAdapter; /** @@ -70,7 +69,7 @@ public static Set getDependenciesOfJar( final InputStream pInputStream ) public static Set getDependenciesOfClass( final InputStream pInputStream ) throws IOException { final DependenciesClassAdapter v = new DependenciesClassAdapter(); - new ClassReader( pInputStream ).accept( v, ClassReader.EXPAND_FRAMES ); + v.accept(pInputStream.readAllBytes()); final Set depNames = v.getDependencies(); return depNames; } diff --git a/src/test/java/org/vafer/jdependency/ClazzpathUnitTestCase.java b/src/test/java/org/vafer/jdependency/ClazzpathUnitTestCase.java index d756a318..ef07e9d5 100644 --- a/src/test/java/org/vafer/jdependency/ClazzpathUnitTestCase.java +++ b/src/test/java/org/vafer/jdependency/ClazzpathUnitTestCase.java @@ -184,10 +184,10 @@ public void testDependencies() throws IOException { final ClazzpathUnit u = cp.addClazzpathUnit(resourceFile("jar1.jar")); final Set deps = u.getDependencies(); - assertEquals(116, deps.size()); + assertEquals(115, deps.size()); final Set transitiveDeps = u.getTransitiveDependencies(); - assertEquals(116, transitiveDeps.size()); + assertEquals(115, transitiveDeps.size()); } From a67ebd4913d85929d983a6b2f6681e9f925bce10 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 02:48:17 +0000 Subject: [PATCH 3/3] Simplify DependenciesClassAdapter using constant pool scan (ClassEntry + Utf8Entry) Co-authored-by: Goooler <10363352+Goooler@users.noreply.github.com> --- .../asm/DependenciesClassAdapter.java | 111 +++--------------- .../jdependency/ClazzpathUnitTestCase.java | 4 +- 2 files changed, 19 insertions(+), 96 deletions(-) diff --git a/src/main/java/org/vafer/jdependency/asm/DependenciesClassAdapter.java b/src/main/java/org/vafer/jdependency/asm/DependenciesClassAdapter.java index 5fe128c9..ce1b29cd 100644 --- a/src/main/java/org/vafer/jdependency/asm/DependenciesClassAdapter.java +++ b/src/main/java/org/vafer/jdependency/asm/DependenciesClassAdapter.java @@ -15,15 +15,11 @@ */ package org.vafer.jdependency.asm; -import java.lang.classfile.Attributes; import java.lang.classfile.ClassFile; import java.lang.classfile.ClassModel; -import java.lang.classfile.FieldModel; -import java.lang.classfile.MethodModel; import java.lang.classfile.constantpool.ClassEntry; import java.lang.classfile.constantpool.PoolEntry; -import java.lang.constant.ClassDesc; -import java.lang.constant.MethodTypeDesc; +import java.lang.classfile.constantpool.Utf8Entry; import java.util.HashSet; import java.util.Set; import java.util.regex.Matcher; @@ -34,108 +30,35 @@ */ public final class DependenciesClassAdapter { - private static final Pattern SIGNATURE_CLASS_PATTERN = - Pattern.compile("L([a-zA-Z0-9_$]+(?:/[a-zA-Z0-9_$]+)*)"); + private static final Pattern DESCRIPTOR_PATTERN = Pattern.compile("L([a-zA-Z0-9_/\\$]+);"); private final Set classes = new HashSet<>(); public void accept(byte[] classBytes) { ClassModel cm = ClassFile.of().parse(classBytes); - for (PoolEntry pe : cm.constantPool()) { if (pe instanceof ClassEntry ce) { - collectFromInternalName(ce.asInternalName()); - } - } - - for (FieldModel fm : cm.fields()) { - collectFromClassDesc(fm.fieldTypeSymbol()); - fm.findAttribute(Attributes.signature()).ifPresent(sa -> - collectFromSignature(sa.signature().stringValue())); - fm.findAttribute(Attributes.runtimeVisibleAnnotations()).ifPresent(a -> - a.annotations().forEach(ann -> collectFromClassDesc(ann.classSymbol()))); - fm.findAttribute(Attributes.runtimeInvisibleAnnotations()).ifPresent(a -> - a.annotations().forEach(ann -> collectFromClassDesc(ann.classSymbol()))); - } - - for (MethodModel mm : cm.methods()) { - collectFromMethodTypeDesc(mm.methodTypeSymbol()); - mm.findAttribute(Attributes.exceptions()).ifPresent(ea -> - ea.exceptions().forEach(ce -> collectFromInternalName(ce.asInternalName()))); - mm.findAttribute(Attributes.signature()).ifPresent(sa -> - collectFromSignature(sa.signature().stringValue())); - mm.findAttribute(Attributes.runtimeVisibleAnnotations()).ifPresent(a -> - a.annotations().forEach(ann -> collectFromClassDesc(ann.classSymbol()))); - mm.findAttribute(Attributes.runtimeInvisibleAnnotations()).ifPresent(a -> - a.annotations().forEach(ann -> collectFromClassDesc(ann.classSymbol()))); - mm.findAttribute(Attributes.runtimeVisibleParameterAnnotations()).ifPresent(a -> - a.parameterAnnotations().forEach(list -> - list.forEach(ann -> collectFromClassDesc(ann.classSymbol())))); - mm.findAttribute(Attributes.runtimeInvisibleParameterAnnotations()).ifPresent(a -> - a.parameterAnnotations().forEach(list -> - list.forEach(ann -> collectFromClassDesc(ann.classSymbol())))); - } - - cm.findAttribute(Attributes.signature()).ifPresent(sa -> - collectFromSignature(sa.signature().stringValue())); - cm.findAttribute(Attributes.runtimeVisibleAnnotations()).ifPresent(a -> - a.annotations().forEach(ann -> collectFromClassDesc(ann.classSymbol()))); - cm.findAttribute(Attributes.runtimeInvisibleAnnotations()).ifPresent(a -> - a.annotations().forEach(ann -> collectFromClassDesc(ann.classSymbol()))); - } - - private void collectFromInternalName(String internalName) { - if (internalName.startsWith("[")) { - collectFromDescriptor(internalName); - } else { - classes.add(internalName.replace('/', '.')); - } - } - - private void collectFromDescriptor(String descriptor) { - int i = 0; - while (i < descriptor.length()) { - char c = descriptor.charAt(i); - if (c == 'L') { - int end = descriptor.indexOf(';', i + 1); - if (end != -1) { - classes.add(descriptor.substring(i + 1, end).replace('/', '.')); - i = end + 1; + String name = ce.asInternalName(); + if (name.startsWith("[")) { + Matcher m = DESCRIPTOR_PATTERN.matcher(name); + while (m.find()) { + classes.add(m.group(1).replace('/', '.')); + } } else { - i++; + classes.add(name.replace('/', '.')); + } + } else if (pe instanceof Utf8Entry ue) { + String str = ue.stringValue(); + if (str.indexOf('L') != -1 && str.indexOf(';') != -1) { + Matcher m = DESCRIPTOR_PATTERN.matcher(str); + while (m.find()) { + classes.add(m.group(1).replace('/', '.')); + } } - } else { - i++; - } - } - } - - private void collectFromClassDesc(ClassDesc desc) { - if (desc.isArray()) { - collectFromClassDesc(desc.componentType()); - } else if (!desc.isPrimitive()) { - String descriptor = desc.descriptorString(); - if (descriptor.startsWith("L") && descriptor.endsWith(";")) { - classes.add(descriptor.substring(1, descriptor.length() - 1).replace('/', '.')); } } } - private void collectFromMethodTypeDesc(MethodTypeDesc desc) { - for (ClassDesc param : desc.parameterList()) { - collectFromClassDesc(param); - } - collectFromClassDesc(desc.returnType()); - } - - private void collectFromSignature(String signature) { - if (signature == null) return; - Matcher m = SIGNATURE_CLASS_PATTERN.matcher(signature); - while (m.find()) { - classes.add(m.group(1).replace('/', '.')); - } - } - public Set getDependencies() { return classes; } diff --git a/src/test/java/org/vafer/jdependency/ClazzpathUnitTestCase.java b/src/test/java/org/vafer/jdependency/ClazzpathUnitTestCase.java index ef07e9d5..d756a318 100644 --- a/src/test/java/org/vafer/jdependency/ClazzpathUnitTestCase.java +++ b/src/test/java/org/vafer/jdependency/ClazzpathUnitTestCase.java @@ -184,10 +184,10 @@ public void testDependencies() throws IOException { final ClazzpathUnit u = cp.addClazzpathUnit(resourceFile("jar1.jar")); final Set deps = u.getDependencies(); - assertEquals(115, deps.size()); + assertEquals(116, deps.size()); final Set transitiveDeps = u.getTransitiveDependencies(); - assertEquals(115, transitiveDeps.size()); + assertEquals(116, transitiveDeps.size()); }