読者です 読者をやめる 読者になる 読者になる
無料で使えるシステムトレードフレームワーク「Jiji」 をリリースしました!

・OANDA Trade APIを利用した、オープンソースのシステムトレードフレームワークです。
・自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんに、自動取引を開始できます。

Javaメソッドの呼び出し元をツリー表示するスクリプト

Ruby Java

JavaClassを使って指定メソッドの呼び出し元を探索しツリー表示するスクリプトを書いてみました。

  • 引数で指定されたパス以下の*.class,および*.jarに含まれるクラスファイルを解析し、
  • メソッドの呼び出しコードを収集。
  • 収集した情報を再帰的に探索し、呼び出し元メソッドをツリー形式で出力します。

使い方

$ ./caller.rb <クラスが置かれたディレクトリorJarファイル> <呼び出し元を探すメソッド>
java.Util.ArrayList#indexOf(Ljava/lang/Object;)I
  • クラスが置かれたディレクトリ or Jarファイルは、「;」区切りで複数指定可能です。

具体例

↓のようなクラスがあったとして、

package com.example.caller;

public class CallSample {
    static class A {
        void aaa() {
            new B().bbb();
        }
        void aaa2() {
            new C().ccc();
        }
        void aaa3() {
            aaa2();
        }
    }
    static class B {
        void bbb() {
            new C().ccc();
        }
    }
    static class C {
        void ccc() {}
    }
}

C#ccc()」の呼び出し元を探索すると、

$ ./caller.rb "../lib;../classes" "com.example.caller.CallSample\$C#ccc()V"

以下が出力されます。

com.example.caller.CallSample$C#ccc()V
├com.example.caller.CallSample$A#aaa2()V
│└com.example.caller.CallSample$A#aaa3()V
└com.example.caller.CallSample$B#bbb()V
 └com.example.caller.CallSample$A#aaa()V

実装

次のとおりです。

#!/usr/bin/ruby

require "rubygems"
require "zip/zip"
require "javaclass"
require "kconv"

# クラスプール
class ClassPool
  def initialize
    @classes = {}
    @classes_r = {}
  end
  def <<( jc )
    jc.methods.each {|m|
      key = jc.name + "#" + m.name + m.descriptor
      @classes[key] ||= []
      next unless m.attributes.key? "Code"
      m.attributes["Code"].codes.each {|code|
        case code.opcode
          when 0xB6,0xB7,0xB8,0xB9
            m = jc.get_constant( code.operands[0].value )
            add_caller( key,
              m.class_name.name + "#" +
              m.name_and_type.name + m.name_and_type.descriptor
            )
          when 0xB2,0xB3,0xB4,0xB5
            m = jc.get_constant( code.operands[0].value )
            add_caller( key,
              m.class_name.name + "." + m.name_and_type.name
            )
        end
      }
    }
  end
  attr_reader :classes
  attr_reader :classes_r
private
  def add_caller( from, to )
    @classes[from] ||= []
    @classes[from] << to
    @classes_r[to] ||= []
    @classes_r[to] << from
  end
end

#ノード
class Node
  def initialize( name  )
    @name = name
    @children = []
  end
  def <<(child)
    @children << child
  end
  def to_s( indent="" )
    str = indent.dup
    str << name.to_s << "\n"
    child_indent = indent.gsub(//, "").gsub(//, " ")
    @children.each_index {|i|
       next if children[i] == nil
       tmp = child_indent + ( i >= children.length-1 ? "" : "" )
       str << children[i].to_s( tmp )
    }
    return str
  end
  attr :name, true
  attr :children, true
end

#呼び出し元を収集する。
def collect( pool, start, stop=[], node=nil, checked=[] )
  node ||= Node.new(start.to_s)
  return node unless pool.classes_r.key? start
  pool.classes_r[start].each {|caller|
    next if checked.find{|i| i==caller }
    next if stop && stop.find{|i| caller =~ i }
    checked.push start.to_s
    node << collect( pool, caller, stop, Node.new( caller ), checked ) 
    checked.pop
  }
  return node
end

# クラス情報を収集
pool = ClassPool.new
JavaClass::Utils.each_class( *ARGV[0].split(";") ){|jc|
  pool << jc
}
# 呼び出し元をツリー表示
puts collect(pool,  ARGV[1]).to_s

なお、「JavaClass」のGemはGemcutterにアップロードしたので、そちらから取得できます。同名のGemがすでにあったので「unageanu-javaclass」として登録しています。

$ gem install unageanu-javaclass

「require」するときは「require 'javaclass'」です。

require 'javaclass'