Thursday, September 22, 2011

JRuby + Asm: easy processing of .class files

I started working with Asm this week.  After playing with it for a bit, it seemed obvious that JRuby would be a sweet spot for Asm code; very often, you need to implement only a small fraction of the interfaces that Asm uses.

Here’s my first JRuby effort with Asm.  It’s straightforward and doesn’t do much yet – just outputs the contents of the .class files given on the command line.  Here’s the JRuby:

require 'java'
require 'asm-3.3.1'
require 'pp'

# This lets me type org.objectweb.asm::... instead of Java::OrgObjectwebAsm::...
def org
  Java::Org
end

# This lets me use ClassReader without typing out org.objectweb.asm::ClassReader
java_import org.objectweb.asm::ClassReader

# Ruby supports mixins.  Both visitor classes (class and method) use this method_missing
# call.
#
# The generic visitor just prints information for any call to a visitor* method.
module GenericVisitor
  def method_missing *args
    if args.first.to_s =~ /visit.*/
      puts "In type: #{self.class} call to " + args.to_s
    else
      super
    end
  end
end

class SampleMethodVisitor
  include org.objectweb.asm::MethodVisitor
  include GenericVisitor
end

class SampleClassVisitor
  include org.objectweb.asm::ClassVisitor
  include GenericVisitor
  
  # we need to keep a copy of our method visitor
  def initialize args
    @method_visitor = args[:method_visitor]
  end
  
  def visit_method *args
    # The output has methods seperated with an easy-to-see string
    puts "visit method " + args.to_s + "================================"
      
    # Asm wants ClassVisitor#visitMethod to return a method visitor 
    @method_visitor
  end
end

# Loop through each .class file given on the command line
ARGV.each do |classfile|
  puts " ---------------- Reading file #{classfile}"
  f = java.io.File.new classfile
  fis = java.io.FileInputStream.new f
  
  class_reader = ClassReader.new fis
  
  v = SampleClassVisitor.new method_visitor: SampleMethodVisitor.new
  
  class_reader.accept v, 0
  
  puts
end

And here’s the output:

---------------- Reading file /Users/james/experimements/AsmSample/bin/com/restphone/classSignature/SampleOne.class
In type: SampleClassVisitor call to [:visit, 50, 33, "com/restphone/classSignature/SampleOne", nil, "java/lang/Object", #<#<Class:0x12690ed81>:0x28b53b32>]
In type: SampleClassVisitor call to [:visitSource, "SampleOne.java", nil]
In type: SampleClassVisitor call to [:visitField, 9, "x", "Ljava/lang/Integer;", nil, nil]
visit method [8, "<clinit>", "()V", nil, nil]================================
In type: SampleMethodVisitor call to [:visitCode]
In type: SampleMethodVisitor call to [:visitLabel, #<Java::OrgObjectwebAsm::Label:0x236954e1>]
In type: SampleMethodVisitor call to [:visitLineNumber, 4, #<Java::OrgObjectwebAsm::Label:0x236954e1>]
In type: SampleMethodVisitor call to [:visitFieldInsn, 178, "com/restphone/classSignature/SampleTwo", "y", "Ljava/lang/Integer;"]
In type: SampleMethodVisitor call to [:visitFieldInsn, 179, "com/restphone/classSignature/SampleOne", "x", "Ljava/lang/Integer;"]
In type: SampleMethodVisitor call to [:visitLabel, #<Java::OrgObjectwebAsm::Label:0x643f58bb>]
In type: SampleMethodVisitor call to [:visitLineNumber, 3, #<Java::OrgObjectwebAsm::Label:0x643f58bb>]
In type: SampleMethodVisitor call to [:visitInsn, 177]
In type: SampleMethodVisitor call to [:visitMaxs, 1, 0]
In type: SampleMethodVisitor call to [:visitEnd]
visit method [1, "<init>", "()V", nil, nil]================================
In type: SampleMethodVisitor call to [:visitCode]
In type: SampleMethodVisitor call to [:visitLabel, #<Java::OrgObjectwebAsm::Label:0x1d7aaa0e>]
In type: SampleMethodVisitor call to [:visitLineNumber, 3, #<Java::OrgObjectwebAsm::Label:0x1d7aaa0e>]
In type: SampleMethodVisitor call to [:visitVarInsn, 25, 0]
In type: SampleMethodVisitor call to [:visitMethodInsn, 183, "java/lang/Object", "<init>", "()V"]
In type: SampleMethodVisitor call to [:visitInsn, 177]
In type: SampleMethodVisitor call to [:visitLabel, #<Java::OrgObjectwebAsm::Label:0x13ad9b0f>]
In type: SampleMethodVisitor call to [:visitLocalVariable, "this", "Lcom/restphone/classSignature/SampleOne;", nil, #<Java::OrgObjectwebAsm::Label:0x1d7aaa0e>, #<Java::OrgObjectwebAsm::Label:0x13ad9b0f>, 0]
In type: SampleMethodVisitor call to [:visitMaxs, 1, 1]
In type: SampleMethodVisitor call to [:visitEnd]
visit method [1, "doubleTheValue", "(Ljava/lang/Integer;)Ljava/lang/Integer;", nil, nil]================================
In type: SampleMethodVisitor call to [:visitCode]
In type: SampleMethodVisitor call to [:visitLabel, #<Java::OrgObjectwebAsm::Label:0x9eae15f>]
In type: SampleMethodVisitor call to [:visitLineNumber, 7, #<Java::OrgObjectwebAsm::Label:0x9eae15f>]
In type: SampleMethodVisitor call to [:visitVarInsn, 25, 1]
In type: SampleMethodVisitor call to [:visitMethodInsn, 182, "java/lang/Integer", "intValue", "()I"]
In type: SampleMethodVisitor call to [:visitInsn, 5]
In type: SampleMethodVisitor call to [:visitInsn, 104]
In type: SampleMethodVisitor call to [:visitMethodInsn, 184, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;"]
In type: SampleMethodVisitor call to [:visitInsn, 176]
In type: SampleMethodVisitor call to [:visitLabel, #<Java::OrgObjectwebAsm::Label:0x2569a1c5>]
In type: SampleMethodVisitor call to [:visitLocalVariable, "this", "Lcom/restphone/classSignature/SampleOne;", nil, #<Java::OrgObjectwebAsm::Label:0x9eae15f>, #<Java::OrgObjectwebAsm::Label:0x2569a1c5>, 0]
In type: SampleMethodVisitor call to [:visitLocalVariable, "x", "Ljava/lang/Integer;", nil, #<Java::OrgObjectwebAsm::Label:0x9eae15f>, #<Java::OrgObjectwebAsm::Label:0x2569a1c5>, 1]
In type: SampleMethodVisitor call to [:visitMaxs, 2, 2]
In type: SampleMethodVisitor call to [:visitEnd]
In type: SampleClassVisitor call to [:visitEnd]

Incidentally, I used my new Eclipse plugin to send the current selection through a filter to html-escape the text.  Check it out on github.

No comments: