/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * BytecodeDebuggingUtilities.java * Created: Jan 24, 2005 * By: Bo Ilic */ package org.openquark.cal.internal.javamodel; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.util.List; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; 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.BasicVerifier; import org.objectweb.asm.util.ASMifierClassVisitor; import org.objectweb.asm.util.CheckClassAdapter; import org.objectweb.asm.util.TraceClassVisitor; import org.openquark.util.FileSystemHelper; /** * Contains various helpers for debugging Java bytecode. The bytecode could have been * generated via a variety of means e.g. using the ASMJavaByteCodeGenerator, or the javac compiler, * (or the now-removed bcel based java bytecode generator). * * These are essentially wrappers around the bytecode debugging facilities provided by ASM. * * The use of these facilities are controlled by AsmJavaByteCodeGenerator.DEBUG_GENERATED_BYTECODE, * JavaBytecodeGenerator.DEBUG_GENERATED_BYTECODE and JavaSourceGenerator.DEBUG_GENERATED_BYTECODE. * * A useful technique is to compare the disassembly produced by javac and (say) asm, on a folder by folder * basis using a differencing tools such as BeyondCompare. * * @author Bo Ilic */ public class BytecodeDebuggingUtilities { /** * An ASMifierClassVisitor that can skip over the inner class annotations (denoted by visitInnerClass calls). * javac and the java compiler in eclipse compiler inner class annotations differently. * * javac annotates for every inner class reference occurring within the class file e.g. a field declared of inner class type, * an instance of expression of inner class type, a throws declaration on a method where an inner class is thrown. * * eclipse's java compiler annotates the class itself if it is an inner class, and all top-level inner classes within the class itself. * * The problem with the javac approach is that one of the attributes passed is the modifiers that the inner class was declared with in its * enclosing class. These are not easily accessible from our java source model! * * @author Bo Ilic */ private static class FilteringASMifierClassVisitor extends ASMifierClassVisitor { private final boolean skipInnerClassAttributes; FilteringASMifierClassVisitor (PrintWriter pw, boolean skipInnerClassAttributes) { super(pw); this.skipInnerClassAttributes = skipInnerClassAttributes; } /* (non-Javadoc) * @see org.objectweb.asm.util.ASMifierClassVisitor#visitInnerClass(java.lang.String, java.lang.String, java.lang.String, int) */ @Override public void visitInnerClass(String arg0, String arg1, String arg2, int arg3) { if (!skipInnerClassAttributes) { super.visitInnerClass(arg0, arg1, arg2, arg3); } } } /** * A TraceClassVisitor that can skip over the inner class annotations. * javac and the java compiler in eclipse compiler inner class annotations differently. * * @author Bo Ilic */ private static class FilteringTraceClassVisitor extends TraceClassVisitor { private final boolean skipInnerClassAttributes; FilteringTraceClassVisitor (ClassVisitor cv, PrintWriter pw, boolean skipInnerClassAttributes) { super(cv, pw); this.skipInnerClassAttributes = skipInnerClassAttributes; } /* (non-Javadoc) * @see org.objectweb.asm.util.ASMifierClassVisitor#visitInnerClass(java.lang.String, java.lang.String, java.lang.String, int) */ @Override public void visitInnerClass(String arg0, String arg1, String arg2, int arg3) { if (!skipInnerClassAttributes) { super.visitInnerClass(arg0, arg1, arg2, arg3); } } } /** * Generates a text file whose contents are the sequence of calls to the ASM library neeeded to generate the * given byteCode. Note: the bytecode need not have been generated using the ASM library. * @param pathName where to save the resulting asmified text * @param byteCode * @param skipDebugOpCodes it is useful to set this to true when comparing the output of javac, which contains considerable * debug info, with that of direct bytecode generators, which contains very little. * @param skipInnerClassAttributes skip visiting the inner class attributes. These appear to be essentially ignored by the java * runtime. javac and eclipse's java compiler handle them differently. ASM follows the pattern of eclipse's java compiler, which * is easier to implement and also seems to make more sense. */ public static void dumpAsmifiedText(String pathName, byte[] byteCode, boolean skipDebugOpCodes, boolean skipInnerClassAttributes) { ClassReader cr = new ClassReader(byteCode); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); PrintWriter pw = new PrintWriter(byteArrayOutputStream); int flags = ClassReader.SKIP_FRAMES; if (skipDebugOpCodes) { flags |= ClassReader.SKIP_DEBUG; } cr.accept(new FilteringASMifierClassVisitor(pw, skipInnerClassAttributes), flags); pw.flush(); pw.close(); writeFile(pathName, byteArrayOutputStream.toByteArray()); } /** * Generates a text file containing the disassembly (into a byte-code assembly language) of some bytecode. * @param pathName where to save the resulting disassembly text * @param byteCode * @param skipDebugOpCodes it is useful to set this to true when comparing the output of javac, which contains considerable * debug info, with that of direct bytecode generators, which contains very little. * @param skipInnerClassAttributes skip visiting the inner class attributes. These appear to be essentially ignored by the java * runtime. javac and eclipse's java compiler handle them differently. ASM follows the pattern of eclipse's java compiler, which * is easier to implement and also seems to make more sense. */ public static void dumpDisassembledText(String pathName, byte[] byteCode, boolean skipDebugOpCodes, boolean skipInnerClassAttributes) { ClassReader cr = new ClassReader(byteCode); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); PrintWriter pw = new PrintWriter(byteArrayOutputStream); int flags = ClassReader.SKIP_FRAMES; if (skipDebugOpCodes) { flags |= ClassReader.SKIP_DEBUG; } cr.accept(new FilteringTraceClassVisitor(null, pw, skipInnerClassAttributes), flags); pw.flush(); pw.close(); writeFile(pathName, byteArrayOutputStream.toByteArray()); } private static void writeFile(String pathName, byte[] contents) { File file = new File(pathName); OutputStream out = null; try { File folder = file.getParentFile(); FileSystemHelper.ensureDirectoryExists(folder); file.createNewFile(); out = new BufferedOutputStream(new FileOutputStream(file)); out.write(contents); } catch (FileNotFoundException e) { throw new RuntimeException ("Unable to find file: " + file.toString()); } catch (IOException e) { throw new RuntimeException ("Error writing to file: " + file.toString()); } finally { // The dump() method also closes the output stream, but this is bad practise. // Let's do the right thing and call close() here as well. if (out != null) { try { out.close(); } catch (IOException ioe) { // Nothing can really be done at this point, and we don't bother reporting the error. } } } } /** * Performs some basic verification tests on bytecode using ASMs BasicVerifier class. * Will generate a variety of exceptions if the verification tests fail. * * One handy feature is that the dependee .class files do not need to actually exist, so this * verifier can be used in a dynamic context, however, it therefore doesn't do the full * amount of verification that, for example, ASM's SimpleVerifier class does or that * Sun's runtime does. * * One interesting fact is that it *does* do some verification that Sun's verifier doesn't do. * For example, Sun's verifier allows you to set the static modifier on a class access flag. This * is technically illegal (although it naturally arises when static inner classes are encoded). * * @param pathName used only for display in error messages. * @param bytecode */ public static void verifyClassFileFormat(String pathName, byte[] bytecode) { ClassReader cr = new ClassReader(bytecode); ClassNode classNode = new ClassNode(); final int flags = 0; // Don't skip anything. cr.accept(new CheckClassAdapter(classNode), flags); List<?> methods = classNode.methods; for (int i = 0; i < methods.size(); ++i) { MethodNode method = (MethodNode)methods.get(i); if (method.instructions.size() > 0) { Analyzer a = new Analyzer(new BasicVerifier()); try { a.analyze(classNode.name, method); continue; } catch (Exception e) { System.out.println("Problems found checking: " + pathName + "."); e.printStackTrace(); } } } } }