Module: NeuronCheckSystem
- Defined in:
- lib/neuroncheck/error.rb,
lib/neuroncheck/utils.rb,
lib/neuroncheck/kernel.rb,
lib/neuroncheck/plugin.rb,
lib/neuroncheck/matcher.rb,
lib/neuroncheck/cond_block.rb,
lib/neuroncheck/declaration.rb
Defined Under Namespace
Modules: DeclarationMethods, Kernel, Keywords, Plugin, Utils Classes: CondBlockContext, Declaration, DeclarationContext, DeclarationError, ExceptionBase, KeywordPluginMatcher, KindOfMatcher, MatcherBase, ObjectIdenticalMathcer, OrMatcher, PluginError, RangeMatcher, RegexpMatcher, SelfMatcher, ValueEqualMatcher
Constant Summary collapse
- TRACE_POINT =
チェック処理を差し込むためのTracePointを作成
TracePoint.new(:call, :return) do |tp| cls = tp.defined_class # メソッドに紐付けられた宣言を取得。取得できなければ終了 decls = METHOD_DECLARATIONS[cls] next unless decls decl = decls[tp.method_id] next unless decl # メソッドの宣言情報を取得 (宣言があれば、メソッドの定義情報も必ず格納されているはずなので、取得成否チェックはなし) met = decl.assigned_method # ここからの処理はイベント種別で分岐 case tp.event when :call # タイプが「メソッドの呼び出し」の場合の処理 self.trace_method_call(tp.self, decl, met, tp.binding) when :return # タイプが「メソッドの終了」の場合の処理 # 「次の1回はスキップする」フラグがONであればスキップ if @skip_next_return then @skip_next_return = false next else ex = self.trace_method_return(tp.self, decl, met, tp.binding, tp.return_value) if ex then # raiseで例外を脱出した場合、なぜかもう1回returnイベントが呼ばれるため、次の1回はスキップするように設定 @skip_next_return = true # 例外を発生させる raise ex end end end end
- METHOD_DECLARATIONS =
全メソッド宣言のリスト
{}
- ATTR_DECLARATIONS =
全属性制限と対応するメソッド情報のリスト
{}
- LOCAL_VARIABLE_GET_USABLE =
bindingクラスに local_variable_get が定義されているかどうかを判定し、その判定結果を定数に記録 (Ruby 2.1以降では定義されているはず)
(binding.respond_to?(:local_variable_get))
- RUBY_TOPLEVEL =
self
Class Method Summary collapse
-
.get_appropriate_matcher(expected, declared_caller_locations) ⇒ Object
期待する値に対応する、適切なマッチャを取得.
-
.get_local_variable_from_binding(target_binding, var_name) ⇒ Object
指定したbindingから、指定した名前のローカル変数を取得する (Binding#local_variable_getが定義されているかどうかで実装を変える).
-
.initialize_module_for_neuron_check(mod_or_class) ⇒ Object
モジュール/クラス1つに対して、NeuronCheck用の初期化を行う.
-
.make_cond_block_context(method_self, context_caption, param_values, allow_instance_method) ⇒ Object
事前条件/事後条件の実行用コンテキストを構築する.
-
.trace_method_call(method_self, declaration, met, method_binding) ⇒ Object
メソッド呼び出し時のチェック処理 (現段階ではattrメソッドの呼び出しでは発生しないことに注意。Ruby 2.1で確認).
-
.trace_method_return(method_self, declaration, met, method_binding, return_value) ⇒ Object
メソッド終了時のチェック処理.
Class Method Details
.get_appropriate_matcher(expected, declared_caller_locations) ⇒ Object
期待する値に対応する、適切なマッチャを取得
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# 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) ⇒ Object
指定したbindingから、指定した名前のローカル変数を取得する (Binding#local_variable_getが定義されているかどうかで実装を変える)
256 257 258 259 260 261 262 263 |
# 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) ⇒ Object
モジュール/クラス1つに対して、NeuronCheck用の初期化を行う
431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 |
# 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) ⇒ Object
事前条件/事後条件の実行用コンテキストを構築する
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 |
# 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) ⇒ Object
メソッド呼び出し時のチェック処理 (現段階ではattrメソッドの呼び出しでは発生しないことに注意。Ruby 2.1で確認)
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 |
# 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.(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.(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.(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.(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) ⇒ Object
メソッド終了時のチェック処理
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 |
# 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.(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 |