module NeuronCheckSystem
Constants
- ATTR_DECLARATIONS
全属性制限と対応するメソッド情報のリスト
- LOCAL_VARIABLE_GET_USABLE
bindingクラスに local_variable_get が定義されているかどうかを判定し、その判定結果を定数に記録 (Ruby 2.1以降では定義されているはず)
- METHOD_DECLARATIONS
全メソッド宣言のリスト
- RUBY_TOPLEVEL
- TRACE_POINT
チェック処理を差し込むためのTracePointを作成
Public Class Methods
get_appropriate_matcher(expected, declared_caller_locations)
click to toggle source
期待する値に対応する、適切なマッチャを取得
# File lib/neuroncheck/matcher.rb, line 7 def self.get_appropriate_matcher(expected, declared_caller_locations) case expected when DeclarationContext # 誤って「self」と記載した場合 raise DeclarationError, "`self` cannot be used in declaration - use `:self` instead" when :self # self SelfMatcher.new(declared_caller_locations) # 値がselfであるかどうかチェック when String, Symbol, Integer ValueEqualMatcher.new(expected, declared_caller_locations) # 値が等しいかどうかをチェック when true, false, nil ObjectIdenticalMathcer.new(expected, declared_caller_locations) # オブジェクトが同一かどうかをチェック when Class, Module KindOfMatcher.new(expected, declared_caller_locations) # 所属/継承しているかどうかをチェック when Range RangeMatcher.new(expected, declared_caller_locations) # 範囲チェック when Regexp RegexpMatcher.new(expected, declared_caller_locations) # 正規表現チェック # when Encoding # EncodingMatcher.new(expected, declared_caller_locations) # エンコーディングチェック when Array OrMatcher.new(expected, declared_caller_locations) # ORチェック when Plugin::Keyword # プラグインによって登録されたキーワードの場合 KeywordPluginMatcher.new(expected, declared_caller_locations) else raise DeclarationError, "#{expected.class.name} cannot be usable for NeuronCheck check parameter\n value: #{expected.inspect}" end end
get_local_variable_from_binding(target_binding, var_name)
click to toggle source
指定したbindingから、指定した名前のローカル変数を取得する (Binding#local_variable_getが定義されているかどうかで実装を変える)
# File lib/neuroncheck/kernel.rb, line 256 def self.get_local_variable_from_binding(target_binding, var_name) if LOCAL_VARIABLE_GET_USABLE then target_binding.local_variable_get(var_name) else # local_variable_getが使えない時は、代わりにevalを使用して取得 target_binding.eval(var_name.to_s) end end
initialize_module_for_neuron_check(mod_or_class)
click to toggle source
モジュール/クラス1つに対して、NeuronCheck用の初期化を行う
# File lib/neuroncheck/kernel.rb, line 431 def self.initialize_module_for_neuron_check(mod_or_class) # 2回目以降の初期化であれば何もせずにスルー if mod_or_class.instance_variable_get(:@__neuron_check_initialized) then return end # 宣言とメソッド情報を格納するためのHashを更新 NeuronCheckSystem::METHOD_DECLARATIONS[mod_or_class] = {} NeuronCheckSystem::METHOD_DECLARATIONS[mod_or_class.singleton_class] = {} # 特異メソッド用のクラスも追加 NeuronCheckSystem::ATTR_DECLARATIONS[mod_or_class] = {} # 対象のModule/Classに対する処理 mod_or_class.instance_eval do # 最後に宣言された内容を保持するクラスインスタンス変数(モジュールインスタンス変数)を定義 @__neuron_check_last_declaration = nil # メソッド定義時のフックを一時的に無効化するフラグ @__neuron_check_method_added_hook_enabled = true # 属性の処理を上書きするために差し込む無名モジュールを定義 (prependする) @__neuron_check_attr_check_module = Module.new prepend @__neuron_check_attr_check_module # メソッド定義時のフックなどを定義したモジュールをextend extend NeuronCheckSystem::Kernel # initialize済みフラグON @__neuron_check_initialized = true # 初期化 end end
make_cond_block_context(method_self, context_caption, param_values, allow_instance_method)
click to toggle source
事前条件/事後条件の実行用コンテキストを構築する
# File lib/neuroncheck/kernel.rb, line 405 def self.make_cond_block_context(method_self, context_caption, param_values, allow_instance_method) # ローカル変数指定用の無名モジュールを作成 local_mod = Module.new local_mod.module_eval do # ローカル変数取得用のメソッドを定義する param_values.each_pair do |var_name, value| define_method(var_name) do return value end end end # ブロック実行用のコンテキストを生成し、ローカル変数保持モジュールを差し込む context = CondBlockContext.new(context_caption, method_self, allow_instance_method) context.extend local_mod # 対象のオブジェクトが持つ全てのインスタンス変数を、コンテキストへ引き渡す method_self.instance_variables.each do |var_name| val = method_self.instance_variable_get(var_name) context.instance_variable_set(var_name, val) end return context end
trace_method_call(method_self, declaration, met, method_binding)
click to toggle source
メソッド呼び出し時のチェック処理 (現段階ではattrメソッドの呼び出しでは発生しないことに注意。Ruby 2.1で確認)
# File lib/neuroncheck/kernel.rb, line 266 def self.trace_method_call(method_self, declaration, met, method_binding) param_values = {} # 大域脱出できるようにcatchを使用 (バックトレースを正しく取得するための処置) error_msg = catch(:neuron_check_error_tag) do # 対象メソッドの引数1つごとに処理 met.parameters.each_with_index do |param_info, def_param_index| param_type, param_name = param_info # 実際に渡された引数の値を取得 (デフォルト値の処理も考慮する) param_value = get_local_variable_from_binding(method_binding, param_name) param_values[param_name] = param_value # 指定位置に対応するマッチャが登録されている場合のみ処理 if (matcher = declaration.arg_matchers[def_param_index]) then # 引数の種類で処理を分岐 case param_type when :key # キーワード引数 unless matcher.match?(param_value, self) then context_caption = "argument `#{param_name}' of `#{declaration.signature_caption_name_only}'" @last_argument_error_info = [method_self, met.name] # 引数チェックエラーの発生情報を記録 throw :neuron_check_error_tag, matcher.get_error_message(declaration, context_caption, param_value) end when :keyrest # キーワード引数の可変長部分 # 可変長部分1つごとにチェック param_value.each_pair do |key, value| unless matcher.match?(value, self) then context_caption = "argument `#{param_name}' of `#{declaration.signature_caption_name_only}'" @last_argument_error_info = [method_self, met.name] # 引数チェックエラーの発生情報を記録 throw :neuron_check_error_tag, matcher.get_error_message(declaration, context_caption, value) end end when :rest # 可変長部分 # 可変長引数であれば、受け取った引数1つごとにチェック param_value.each_with_index do |val, i| unless matcher.match?(val, self) then context_caption = "#{NeuronCheckSystem::Utils.ordinalize(def_param_index + 1 + i)} argument `#{param_name}' of `#{declaration.signature_caption_name_only}'" @last_argument_error_info = [method_self, met.name] # 引数チェックエラーの発生情報を記録 throw :neuron_check_error_tag, matcher.get_error_message(declaration, context_caption, val) end end else # 通常の引数/ブロック引数 unless matcher.match?(param_value, self) then context_caption = "#{NeuronCheckSystem::Utils.ordinalize(def_param_index + 1)} argument `#{param_name}' of `#{declaration.signature_caption_name_only}'" @last_argument_error_info = [method_self, met.name] # 引数チェックエラーの発生情報を記録 throw :neuron_check_error_tag, matcher.get_error_message(declaration, context_caption, param_value) end end end end # 事前条件があれば実行 if declaration.precond then # コンテキストを生成 context = make_cond_block_context(method_self, 'precond', param_values, declaration.precond_allow_instance_method) # 条件文実行 context.instance_exec(&(declaration.precond)) end # 最後まで正常実行した場合はnilを返す nil end # catch # エラーメッセージがあればNeuronCheckError発生 if error_msg then raise NeuronCheckError, error_msg, (NeuronCheck.debug? ? caller : caller(3)) end end
trace_method_return(method_self, declaration, met, method_binding, return_value)
click to toggle source
メソッド終了時のチェック処理
# File lib/neuroncheck/kernel.rb, line 347 def self.trace_method_return(method_self, declaration, met, method_binding, return_value) # 処理中に例外が発生した場合、TracePointがreturnとして検知してしまうため # それを防ぐために例外を自前でキャッチ begin # 大域脱出できるようにcatchを使用 (バックトレースを正しく取得するための処置) error_msg = catch(:neuron_check_error_tag) do # 最後に引数チェックエラーが発生しており、selfとメソッド名が同じものであれば、return値のチェックをスキップ # (NeuronCheckError例外を発生させたときにもreturnイベントは発生してしまうため、その対策として) if @last_argument_error_info and @last_argument_error_info[0].equal?(method_self) and @last_argument_error_info[1] == met.name then @last_argument_error_info = nil return end param_values = {} # 対象メソッドの引数1つごとに処理 met.parameters.each_with_index do |param_info, def_param_index| _, param_name = param_info # 実際に渡された引数の値を取得 (デフォルト値の処理も考慮する) param_value = get_local_variable_from_binding(method_binding, param_name) param_values[param_name] = param_value end # 戻り値チェック if (matcher = declaration.return_matcher) then # チェック処理 unless matcher.match?(return_value, method_self) then # エラー throw :neuron_check_error_tag, matcher.get_error_message(declaration, "return value of `#{declaration.signature_caption_name_only}'", return_value) end end # 事後条件があれば実行 if declaration.postcond then # コンテキストを生成 context = make_cond_block_context(method_self, 'postcond', param_values, declaration.postcond_allow_instance_method) # 条件文実行 context.instance_exec(return_value, &(declaration.postcond)) end # 最後まで正常実行した場合はnilを返す nil end # catch # エラーメッセージがあればNeuronCheckError発生 if error_msg then raise NeuronCheckError, error_msg, (NeuronCheck.debug? ? caller : caller(3)) end rescue Exception # 例外が発生した場合はその例外オブジェクトを返す return $! end end