Rubyによる
C言語コード
メトリクス測定

富山Ruby会議01

2019.Nov.03

よしだ たけひこ

自己紹介

職業 : 組込みシステム技術者

産業用プリンターのファームウェアを
C言語C++ で開発しています。

プロッタ

コードメトリクスとは?


  • ソースコード行数 (KLOC)
  • コメント率
  • ネスト数
  • サイクロマティック複雑度

などなど

コードメトリクス計測ツール

静的コード解析ツール

  • PARASOFT C++test
  • QAC / QAC++
  • かぞえチャオ!

一般的なツールでは測れない

  1. バグが見つかってからコード品質計測
  2. 計測対象(バグが見つかったコード)に
    合わせて『モノサシ(計測基準)』を
    カスタマイズ
  3. 『計測』と『分析』を繰り返しながら
    ブラッシュアップ

一般的な静的コード解析ツールは
『計測不能!』と匙を投げる。

投げ出す

Rubyが便利なところ

  1. (十数行の)短いプログラムで
    簡易な計測ツールを実装可能。
  2. コンパイル不要。とりあえず動かして確認。
  3. 正規表現が使える。
    標準 C / C++ 以外では当たり前?
  4. CSV入出力を使いこなして、EXCELと連携させてグラフ作成や報告書作成。

メトリクス計測事例


  1. GOTO文の数
  2. マジックナンバーの数
  3. コンパイルスイッチ #ifndef の数
  4. 文字コード(Shift-JIS, UTF-8, EUC)の分類

計測の具体例

const修飾 (定数宣言) を途中で外す悪弊が蔓延

const char the_parameters[][] = {
    { 0x00, 0x80, 0x00, 0xff, /* 中略 */ },
    { 0x00, 0x8f, 0x00, 0xf0, /* 中略 */ },
    /* 中略 */,
};

void foo (void) {
    /* 中略 */
    ret = bar( (char*) the_parameters[i] );
    /* 中略 */
}

int bar (char* parameters) {
    /* 中略 */
}

この件で何が起きる? 起きない?

《問題が発生しないケース》

  • const修飾を外した呼出先関数で
    ポインタの参照先を書き換えない。

(だったら定数のままで渡せばいいのに…)

この件で何が起きる? 起きない?

《問題が発生するケース》

constだったポインタ参照先を書き換え!

  1. PCアプリ
    • 不正なメモリ領域アクセスエラー
      • メモリセグメンテーションエラーなど
  2. 組込みシステム(ROMブートシステム)
    • ROM書換不能。実行時エラーなし。
      • 動きがおかしくなるだけ。

const修飾を外す
『不正な』キャスト演算を全て探せ!

命令

何を探すか?

命令

計測(検索)対象の候補

  1. 定数宣言を途中で覆すコード
    1. const修飾の外し方は多種多様
    2. キャスト演算の箇所が多い
  2. 定数宣言されたグローバル変数, 配列
    1. (比較的) 数が少ない
    2. 正規表現での検索が用意

計測スクリプトの方針

  1. const修飾された(=定数宣言された)グローバル変数の数と場所(どのソースファイル?)を把握する。
  2. 調査対象の規模と範囲の判明後は人海戦術。
    → 調査範囲と作業時間の見積もりがなければ、調査担当者の割り当てもできない。

やったこと

正規表現によるゴリ押し

  • テキストエディタ・マクロやEXCELマクロ, Pythonでも、同じことはできたが、最後は担当者(私)の好み。
命令

定数定義グローバル変数カウントRubyスクリプト

#!/usr/bin/env ruby

begin
  target_files = Dir.glob("src/**/*.c") + Dir.glob("src/**/*.h")
  target_files.each do |file_name|
    File.open(file_name) do |f|
      count = 0
      f.each_line do |line|
        begin
          line.encode!("UTF-8", "CP932")
          if line.match(/^(static\s+)*const\s+/) then
            count = count + 1
          end
        rescue => e
        end
      end
      puts "#{file_name},#{count}" if count > 0
    end
  end
end

Rubyスクリプト解説

《ファイル抽出》


target_files
    = Dir.glob("src/**/*.c") + Dir.glob("src/**/*.h")
  • 拡張子が "c" と "h" のファイルをピックアップ
  • バイナリファイル(フォント・データなど)が混じっているので適切にピックアップしないとスクリプトが途中で中断。

Rubyスクリプト解説

《文字コード変換》


line.encode!("UTF-8", "CP932")
  • CP932 : Microsoft SHIFT-JIS
  • レガシー化した組込開発環境ではSHIFT-JISが今も現役 (涙)

Rubyスクリプト解説

《正規表現》


line.match(/^(static\s+)*const\s+/)
  • みんな大好き「正規表現」
  • オブジェクト指向やTDDには馴染みの薄い組込み開発者も正規表現ならOK!

Rubyスクリプトの実行結果の例


$ ruby const_counter.rb
src/object.c,       1
src/class.c,        1
src/array.c,        1
src/string.c,       6
src/fmt_fp.c,       1
src/backtrace.c,    1
src/symbol.c,       4
src/proc.c,         1

【註】 mruby のソースコードを例にしています

ヒットが上手くいったコードの例

static const mrb_code each_iseq[] = {
  OP_ENTER, 0x0, 0x00, 0x1,  /* OP_ENTER     0:0:0:0:0:0:1 */
  OP_JMPIF, 0x1, 0x0, 19,    /* OP_JMPIF     R1  19 */
  OP_LOADSELF, 0x3,          /* OP_LOADSELF  R3 */
  OP_LOADSYM, 0x4, 0x0,      /* OP_LOADSYM   R4  :each*/
  /* 中略 */
};

ミスヒットしたコードの例

static const char *
char_adjust(const char *beg, const char *end, const char *ptr)
{
  if ((ptr > beg || ptr < end) && (*ptr & 0xc0) == 0x80) {
    const int utf8_adjust_max = 3;
    /* 中略 */
}
  • ミスヒット(誤カウント)するケースもあるが、あとで人力レビューするためOK!

実際の計測対象の分析結果


項目 計測値
計測対象コード規模 1,200 ファイル
400 KLOC
Rubyスクリプトヒット 300 ファイル
5,000
不適切なconst外し 500
不正な定数の書換え 9

まとめ


  • 作業(含む見積もり)を効率化するためのツールだから100%の正確さは求めない
  • 毎回毎回同じことをするわけではない(と願いたい)から使い捨てと割り切り
  • 品質向上活動には下準備(Yak Shaving)が大切