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

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.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) ⇒ 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.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