javabytecodejava-bytecode-asm

How can I make org.objectweb.asm.util.CheckClassAdapter throw an exception instead of printing errors to stderr?


I am currently utilizing org.objectweb.asm.util.CheckClassAdapter for bytecode verification in my Java project. However, I've observed that this class prints errors to stderr instead of throwing exceptions, which makes it challenging to handle verification failures programmatically.

Here is an example of the existing code snippet:

final byte[] bytes = …; // class bytes are generated previously  
CheckClassAdapter.verify(
    new ClassReader(bytes),
    false,
    new PrintWriter(System.err)
);

It appears that a potential workaround could be:

final byte[] bytes = …; // class bytes are generated previously  
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
CheckClassAdapter.verify(
    new ClassReader(bytes),
    false,
    pw
);
if (sw.toString().contains("AnalyzerException")) {
  System.err.println(sw.toString());
  fail();
}

However, this solution feels like a "crutch" to me. Is there a way to modify or extend the CheckClassAdapter to make it throw an exception when bytecode verification fails, rather than printing errors to stderr? I aim to catch and handle verification errors within my code. Any guidance or examples would be greatly appreciated.


ASM version: org.ow2.asm:asm:9.6


Solution

  • Since I haven't found how I can use CheckClassAdapter to verify bytecode and throw an AnalyzerException exception, I decided to modify the source code of the verify method, as Holger suggested. Here's how you can verify the bytecode:

    /**
     * Verify the bytecode.
     * @param bytes The bytecode to verify.
     */
    private void verify(final byte[] bytes) throws AnalyzerException {
        final ClassNode clazz = new ClassNode();
        new ClassReader(bytes)
            .accept(new CheckClassAdapter(clazz, false), ClassReader.SKIP_DEBUG);
        final Optional<Type> syper = Optional.ofNullable(clazz.superName).map(Type::getObjectType);
        final List<Type> interfaces = clazz.interfaces.stream().map(Type::getObjectType)
            .collect(Collectors.toList());
        for (final MethodNode method : clazz.methods) {
            final SimpleVerifier verifier =
                new SimpleVerifier(
                    Type.getObjectType(clazz.name),
                    syper.orElse(null),
                    interfaces,
                    (clazz.access & Opcodes.ACC_INTERFACE) != 0
                );
            // You might need to set your own ClassLoader here.
            // verifier.setClassLoader(Thread.currentThread().getContextClassLoader());
            new Analyzer<>(verifier).analyze(clazz.name, method);
        }
    }
    

    Imports:

    import org.objectweb.asm.ClassReader;
    import org.objectweb.asm.Opcodes;
    import org.objectweb.asm.Type;
    import org.objectweb.asm.tree.ClassNode;
    import org.objectweb.asm.tree.MethodNode;
    import org.objectweb.asm.tree.analysis.Analyzer;
    import org.objectweb.asm.tree.analysis.AnalyzerException;
    import org.objectweb.asm.tree.analysis.SimpleVerifier;
    import org.objectweb.asm.util.CheckClassAdapter;
    
    

    Also, it's worth mentioning that this method isn't perfect since you generate bytecode and then parse it again. Most probably, you can simplify this process by integrating bytecode verification directly into the generation process using CheckClassAdapter and CheckMethodAdapter classes