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

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

セーフレベルの高い環境から、特定のオブジェクトのメソッド呼び出しを別のセーフレベルで実行するユーティリティ

ネタがないので、昔書いたクラスでも発掘するかな。ということで、前に書いたセーフレベルの高い環境から、特定のオブジェクトのメソッド呼び出しを低いセーフレベルで実行するためのユーティリティです。仕組みは以下のとおり。

  • 低いセーフレベルで動作するスレッドをあらかじめ起動させておき、
  • Permitter#proxyで、指定されたオブジェクトのメソッドの実行を起動しておいたスレッドで行うように改変。
  • メソッド実行を低いセーフレベルで動作するスレッドで行うことで、セーフレベルの高い環境から普通は実行できない処理を含むメソッドを呼び出せるようにします。

サンプル

#任意のセーフレベルでブロックを実行する
def safe( level=4 ) 
  return Thread.fork(level) {|l|
    $SAFE = l
    yield
  }.value
end

class TestClass 
  def test
    puts "test" # IOを行うのでセーフレベルの高い環境で実行するとエラーになる。
  end
end

#TestClassのtestメソッドはセーフレベル4で実行されるとエラーになる。
test = TestClass.new
begin
  safe(4) {
    test.test
  }
   raise "ここにはこないはず"
rescue SecurityError
end

#Permitterでプロキシをかける。
#プロキシをかけたオブジェクトのメソッドは、セーフレベルの高い環境でも実行を許可できる。
begin
  p = Permitter.new
  permitted = p.proxy( test, [/test/] )
  safe(4) {
    permitted.test # 実行可能
  }
  
  #クラスの改変は不可なのでセーフレベルの高い環境でのプロキシの適用は不可。
  test2 = TestClass.new
  safe(4) {
    begin
       permitted2 = p.proxy( test2, [/test/] )
       raise "ここにはこないはず"
    rescue SecurityError
    end
  }
ensure
  p.close if p
end

実装

require 'thread'
require 'set'

#==任意のオブジェクトのAPIをセーフレベルの高い環境から呼び出せるようにする。
class Permitter
  
  # コンストラクタ
  #pool_size:: スレッドプールのサイズ
  #level:: メソッド実行時のセーフレベル
  def initialize( pool_size=5, level=2 )
    @alive = true
    @alive_mutex = Mutex.new
    @q = Queue.new
    @ts = []
    pool_size.times {
      @ts << Thread.fork {
        $SAFE = level
        while( @alive_mutex.synchronize { @alive } )
          req = @q.pop
          req.exec(self) if req
        end
      }
    }
  end
  # リクエストを追加する。
  def <<(request)
    @q.push request
  end
  # インスタンスを破棄する。不要になった場合に必ず呼び出すこと。
  def close
    @alive_mutex.synchronize {
      @alive = false
    }
    @ts.length.times {
      @q.push nil
    }
    @ts.each {|t| t.join }
  end
  # 指定したインスタンスのAPIをセーフレベルの高い環境から呼び出せるようにする。
  #object:: APIの呼び出しを許可するオブジェクト
  #allows:: 許可するAPIを示す正規表現の配列
  #proxy_result:: 戻り値もプロキシを設定するAPIを示す正規表現の配列
  def proxy( object, allows=[], proxy_result=[])
    clazz = class << object
      include Permitted
      self
    end
    object.permitter = self
    object.permitter_allows = allows
    object.permitter_proxy_result = proxy_result
    object.methods.each {|name|
      next unless allows.find {|a| name.to_s =~ a }

      # もともとのメソッドは、別名でキープしておく
      old = name.to_s + "_without_permittion"
      # 2重のアスペクト適用を防ぐ。
      next if clazz.method_defined? old.to_sym
      
      clazz.__send__(:alias_method, old.to_sym, name.to_sym )
      # インターセプタ適用対象のメソッドを上書きで定義。
      clazz.__send__(:alias_method, name.to_sym, :permitted_request )
    }
    object
  end

  # メソッド呼び出しリクエスト
  class Request
    def initialize( receiver, name, args, block=nil, allows=[], proxy_result=[] )
      @name = name
      @args = args
      @block = block
      @receiver = receiver
      @mutex = Mutex.new
      @cv = ConditionVariable.new
      @finished = false
      @value = nil
      @error = nil
      @allows = allows
      @proxy_result = proxy_result
    end
    # リクエストの完了を待ち、結果を返す。
    def wait
      @mutex.synchronize{
        @cv.wait( @mutex ) until @finished
      }
      raise @error if @error
      @value
    end
    # リクエストを実行する
    def exec( permitter )
      begin
        m = @name + "_without_permittion"
        @value = @block ? @receiver.send( m, *@args, &@block ) : @receiver.send( m, *@args )
        if @proxy_result.find {|a| @name.to_s =~ a }
          @value = permitter.proxy( @value, @allows, @proxy_result )
        end
      rescue Exception
        @error = $!
      ensure
        @mutex.synchronize{
          @finished = true
          @cv.signal
        }
      end
    end
  end
end

module Permitted
  def permitted_request( *args, &block )
    r = Permitter::Request.new( self, _function_name, args, 
      block, @permitter_allows, @permitter_proxy_result )
    @permitter << r
    return r.wait
  end
  def _function_name
    if /^(.+?):(\d+)(?::in `(.*)')?/ =~ caller.first
      return $3
    end
  end
  attr :permitter, true
  attr :permitter_allows, true
  attr :permitter_proxy_result, true
end


セキュリティホールになってないか若干不安なんですが。大丈夫かな。