契約プログラミングでのオーバーライドの実現(D、C++、C#の実装比較)

 オブジェクト指向の定番の入門書OOSCでは、オブジェクト指向契約プログラミングの間に密接な関連付けを行っている。例えば継承によってメソッドをオーバーライドする場合では、Assertion Redeclaration ruleとして以下を示している。

  • 事前条件は、継承元と同等かより弱いもの(or-ed)に置き換えられる。
  • 事後条件は、継承元と同等かより強いもの(and-ed)に置き換えられる。

 今回は、このルールの実現度について、D、C++C#の3つぞれぞれで確認してみる。

結果まとめ

 Dは、OOSCのルール通りにオーバーライドを実装できる。記述も簡潔で、今回のテーマに限定すれば本家Eiffelのように契約プログラミングを行える。
 C++C++11の言語機能を使ったBoost.Contract(Boostに将来マージ予定)によって、OOCS通りの実装ができる。ただし違和感のある実装で、無理やり実現している感が否めない。
 C#Visual Studio拡張機能であるCode Contracts for .NET extensionを追加することで、契約プログラミングを簡潔に実装できるようになる。ただオーバーライド時の事前条件の評価方法が、OOSCのルールやDなどと異なる。

サンプルコード

 最低限の確認として、今回は以下を実装して動かす。

  • Bar::run()が、Foo::run()をオーバーライドする。
  • Bar::run()、Foo::run()はそれぞれ異なる事前条件、事後条件を持つ。
  • 「すべての事前条件・事後条件を満たす」「Bar::run()の事前条件のみ違反」「Bar::run()の事後条件のみ違反」「Foo::run()の事前条件のみ違反」「Foo::run()の事後条件のみ違反」の5パターンの動作を試す。

Dでの実装

 D言語契約プログラミングのサポートに注力しており、コードもすっきり書ける。コードは以下の通り。

import std.stdio;

class Foo
{
public:
    int run(int arg)
        in { //事前条件
            assert(arg % 3 == 0);
        }
        out (result) { //事後条件
            assert(result < 100);
        }
        body { //関数本体
            return arg * 3;
        }
}

class Bar : Foo 
{
public:
    override int run(int arg)
        in { //事前条件
            assert(arg % 2 == 0);
        }
        out (result) { //事後条件
            assert(result > 0);
        }
        body { //関数本体
            return arg * 2;
        }
}

void main()
{
    Foo foo = new Bar();
    writeln(foo.run(48));//success すべての事前条件・事後条件を満たす
    //writeln(foo.run(3));//success サブクラスの事前条件違反
    //writeln(foo.run(-6));//failure サブクラスの事後条件違反
    //writeln(foo.run(2));//success スーパークラスの事前条件違反
    //writeln(foo.run(51));//failure スーパークラスの事後条件違反
}

 Bar::run()の引数に応じた動作は、main()のコメントの「success」「failure」の通り。
 Bar::run()、Foo::run()の事前条件はORで結合されて評価されており、事後条件はANDで結合されて評価されているのがわかる。OOSCに忠実な動作となっている。

C++での実装

 C++では、契約プログラミングのライブラリとしてBoost.Contractが開発されている(正式なBoostへのマージは今後の予定。https://github.com/lcaminiti/boost-contract)。今回はそれを使用して実装した。
 なおC++では直接的にOOSCの契約プログラミングをサポートしていないため、記述がかなり煩雑になる。

#include <iostream>
#include <boost/contract.hpp>

class Foo 
{
protected:
    int result_;
public:
    virtual int run(int arg, boost::contract::virtual_* v = 0) {
        boost::contract::guard c = boost::contract::public_function(v, this)
            .precondition([&] { //事前条件
                BOOST_CONTRACT_ASSERT(arg % 3 == 0);
            })
            .postcondition([&] { //事後条件
                BOOST_CONTRACT_ASSERT(result_ < 100);
            })
        ;
        return (result_ = arg * 3); //本体
    }
};

class Bar 
    #define BASES public Foo
    : BASES
{
public:
    typedef BOOST_CONTRACT_BASE_TYPES(BASES) base_types;
    #undef BASES
    
    virtual int run(int arg, boost::contract::virtual_* v = 0) {
        boost::contract::guard c = boost::contract::public_function<override_run>(v, &Bar::run, this, arg)
            .precondition([&] { // 事前条件
                BOOST_CONTRACT_ASSERT(arg % 2 == 0);
            })
            .postcondition([&] { //事後条件
                BOOST_CONTRACT_ASSERT(result_ > 0);
            })
        ;
        return (result_ = arg * 2); //本体
    }
    BOOST_CONTRACT_OVERRIDE(run)
};

int main()
{
    Bar foo;
    std::cout << foo.run(48) << std::endl;//success すべての事前条件・事後条件満たす
    //std::cout << foo.run(3) << std::endl;//success サブクラスの事前条件違反
    //std::cout << foo.run(-6) << std::endl;//failure サブクラスの事後条件違反
    //std::cout << foo.run(2) << std::endl;//success スーパークラスの事前条件違反
    //std::cout << foo.run(51) << std::endl;//failure スーパークラスの事後条件違反

    return 0;
}

 Bar::run()への引数に応じた挙動は、D言語のものと同じになる。
 そのためC++でもオーバーライドでは契約プログラミングのルールを実現できている。

C#での実装

 C#も直接的にOOSCの契約プログラミングをサポートしていない。ただCode Contracts for .NET extensionを使用することによって、実装が容易になる(なおこのextensionはVC++でもVB.NETでも使える)。

using System;
using System.Diagnostics.Contracts;

class Foo
{
    public virtual int run(int arg)
    {
        Contract.Requires(arg % 3 == 0); // 事前条件
        Contract.Ensures(Contract.Result<int>() < 100); // 事後条件
        return arg * 3;
    }
}
class Bar : Foo
{
    public override int run(int arg)
    {
        Contract.Requires(arg % 2 == 0); // 事前条件
        Contract.Ensures(Contract.Result<int>() > 0); // 事後条件
        return arg * 2;
    }
}

class Program
{
    static void Main(string[] args)
    {
        int ret;
        ret = new Bar().run(48);//success すべての事前条件・事後条件を満たす
        //ret = new Bar().run(3);//failure サブクラスの事前条件違反
        //ret = new Bar().run(-6);//failure サブクラスの事後条件違反
        //ret = new Bar().run(2);//failure スーパークラスの事前条件違反
        //ret = new Bar().run(51);//failure スーパークラスの事後条件違反        
        Console.WriteLine(ret);
    }
}

 Code Contracts for .NETを使った場合、事後条件、事前条件ともに、AND結合されて評価されるようだ。「サブクラスの事前条件のみ違反」「スーパークラスの事前条件のみ違反」のときに、OOSCのルールと異なる判定結果を出している。

Stateflowでの状態遷移モデルの動的シミュレーション

ソフトウェアのモデル駆動開発ツールの一つに、Stateflowがある。

Stateflowは、MATLAB/Simulink上で状態遷移モデルと制御フローモデルを動かすための環境に該当する。
この環境、母体となるMATLAB/Simulinkが組み込み制御システムならあらかたシミュレートできる超高機能ツールなので、ソフトウェア設計モデルのツールとしてはオーバースペックと言えるかもしれない。ただMBSEのようにシステムレベルからモデル駆動やモデルベースの開発を行っている場合は、モデル駆動開発環境として有望な選択肢になると思う。

あまり情報がないので今回簡単に紹介したい。

モデルの記述

題材として、黒線を伝って走るライントレーサーを扱う。
なおMATLAB/Simulinkは、制御理論や信号処理のライブラリが充実しているので制御システムレベルからシミュレートしてこそ価値が出るけれど、今回はあくまで例示としてソフトウェア設計モデルのみを扱う。

全体のシステムのモデルは以下。

f:id:goyoki:20160414005646p:plain

「Line Sensor」がライントレーサーの右側センサ・左側センサの入力で、「Motor Signal」が(現実からかなり簡略化しているけれど)右側モータの駆動信号、左側モータの駆動信号を示す。
真ん中の「Line Tracer」がソフトウェア処理のモデルになる。このモデルを状態遷移モデルで記述・実行できるようにするのがStateflowになる。


「Line Tracer」の中のモデルは以下。ソフトウェアの状態遷移モデルを記述している。なおStateflowでは、状態遷移モデルを状態遷移図でも状態遷移表でも書けるようになっている。

f:id:goyoki:20160418005501p:plain

処理としては、一定周期ごとにセンサ入力を判定し、直進、左折、右折を実行するようにしている。またセンサが両側とも反応しない異常状態では人手で位置直しすることを期待して停止する。

モデルのシミュレーション

システムの「Line Sensor」はSignal Builderと呼ばれるもので、Excelで入力パターンを指定できる。
例として、停止、右折、左折、直線それぞれを網羅するセンサ入力データのパターンを0秒から8秒分まで指定し、その間のモデルの動きをSimulinkでシミュレートする。
シミュレート結果である入出力の推移は以下の様に表示される。

f:id:goyoki:20160418005616p:plain

指定したセンサ入力パターンに応じて、状態遷移モデルが動作し、モータが駆動されていることがわかる。
なおこうした入出力のプローブだけでなく、状態遷移をアニメーションで示したり、構造的な検査を行ったりすることもできる。

憂鬱なExcel作業をPythonで紛らわす

自分の組み込み業界ではやたらExcelが多くて、Excelドキュメントのレビューの機会が度々ある。その中には、ファイル間のトレーサビリティ目視チェックといった、時に刺身タンポポと揶揄されるような気の滅入る作業も少なくない。


こういった作業は、周知の通りだと思うけれど、マイクロソフト系の言語や、PythonRubyなど様々なプログラミング言語Excel操作のライブラリを提供しているおかげで、自動化できることが多い。
そのため基本姿勢として自動化に手を付けてみるのは良いと思う。生産性が上がることが多いのもある。また何より、例えば「Excelの目視レビューでなく、Pythonのコーディングをしている」と思えば気を紛らわせられる、ような気がする。ソフトウェア開発者として精神衛生的に良い。

今回はその自動化の実現手段の一つとして、Pythonのopenpyxlを使ったExcelドキュメントのチェックについて書きたい。

openpyxlの大まかな使い方

openpyxlはPythonExcelの読み書きを行うためのライブラリ。今回は読み取りのみ扱っているけれど、Excelファイルの生成や内容更新もできる。

インストール

インストールはpipで可能(pip install openpyxl実行)

ファイルのロードの内容の表示

openpyxl.load_workbookでファイル名を指定してワークブックを読み出し操作する。
例えばファイル名「仕様書2.xlsx」の、仕様項目定義シートのB3セルの値を表示する場合、以下のように記述する。

from openpyxl import load_workbook

wb = load_workbook(filename = "仕様書2.xlsx", read_only=True)
print(wb["仕様項目定義"]["b3"].value)


複数のセルを扱いたい場合は、以下のようにiter_rows等を使用する

from openpyxl import load_workbook

wb = load_workbook(filename = "仕様書2.xlsx", read_only=True)
#仕様項目定義シートのB1からB20までのセルの値を表示
for id_area in wb["仕様項目定義"].iter_rows("B1:B20"):
    for data in id_area:
        print(data.value)

全シートを参照する場合は、ワークブックオブジェクトのworksheetsに対してfor inループを回す。

from openpyxl import load_workbook
wb = load_workbook(filename = "仕様書2.xlsx", read_only=True)
#全シートのタイトルとA1セルの値を表示
for sheet in wb:
    print(sheet.title)
    print(sheet["A1"].value)

サンプル

複数間のファイルでIDの整合性がとれるかという、よくありがちで憂鬱なチェックをopenpyxlで行う。
具体的には、「仕様書2.xlsx」と「トレーサビリティマトリクス.xlsx」の仕様項目IDが一致しているかを確認する。

仕様項目2.xlsx 仕様項目定義シート
●仕様項目定義
ID 仕様項目詳細
SPEC1 AAA
SPEC2 BBB
SPEC13 CCC
SPEC4 DDD
SPEC5 EEE
SPEC6 FFF
トレーサビリティマトリクス.xlsx トレーサビリティマトリクスシート
仕様項目
SPEC1 SPEC2 SPEC3
要件 REQ1
REQ2
REQ3

確認コード

# coding: shift_jis
from openpyxl import load_workbook

if __name__ == '__main__':

    # トレーサビリティマトリクスから仕様項目IDを抽出
    wb_tm = load_workbook(filename = "トレーサビリティマトリクス.xlsx", read_only=True)
    
    tm_id_list = []
    for id_area in wb_tm["トレーサビリティマトリクス"].iter_rows('D4:AO4'):
        for data in id_area:
            #最初の文字列から空欄までの文字列をピックアップ
            if data.value != "" and data.value != None:
                tm_id_list.append(data.value)
            if data.value == "" and len(tm_id_list) > 0:
                break


    # 仕様書から仕様項目IDを抽出
    wb = load_workbook(filename = "仕様書2.xlsx", read_only=True)

    spec_id_list = []
    in_idarea = False
    for id_area in wb["仕様項目定義"].iter_rows("B1:B500"):
        for data in id_area:
            # タイトル"●仕様項目定義"から空欄までの文字列をピックアップ
            if in_idarea:
                if data.value == "":
                    break;
                if data.value != "ID":
                    spec_id_list.append(data.value)
                        
            if data.value =="●仕様項目定義":
                # 仕様項目IDの記述欄開始
                in_idarea = True
    
    
    if sorted(spec_id_list) == sorted(tm_id_list):
        print("整合")
    else:
        print("不整合")

RL78/G13 Stickでリアルタイムクロックを使ってLチカ

最近、RL78/G13 Stick評価ボード(*)を購入した手始めに、定番のLチカを行ったので手順をメモ。

環境構築

ソフトウェアの方は、ルネサスのサイトからCS+ for CA,CXをダウンロード・インストール。また評価ボードのマニュアルに従って、評価ボードのデバイスドライバをインストール。
ハードウェアの方は、マニュアルに従ってデバッグツールが動作するようにジャンパ設定を行う。

プロジェクトの作成

R5F100LE(64KB)でプロジェクト作成。
次にツールプラグインの管理で、「コード生成プラグイン」を有効化する。
またデバッガはRL78 EZ Emulatorを選択。

コーディング

今回はリアルタイムクロックで2秒周期でLEDを点滅させるプログラムを書く。
レジスタ設定が煩雑なので、今回はCS+のコード生成プラグイン任せでレジスタ設定のコードを生成する。

コード生成プラグインでの生成

コード生成のメニューから、以下を設定する。

  • 端子割り当て設定はデフォルトで確定する。
  • 以下リアルタイムクロック設定
    • クロック設定で、動作モード設定とEVDD設定を「高速メインモード2.7~5.5(V)」、サブシステム・クロック設定を「動作」「XT1発振」にする。
    • リアルタイムクロック動作設定で、動作設定を「使用する」、リアルタイムクロック初期値設定をチェックして任意の日時を指定、アラーム検出機能をチェック、定周期割り込み機能をチェックして「1秒に1度」を選択
  • LED出力設定として、ポート設定で、ポート7のP77を出力に設定する。

設定が終わったら、「コード生成」でコードを生成する。

実装

煩雑なレジスタ設定や割り込み設定は自動生成されているので、あとはリアルタイムクロックの開始処理と、割り込み処理を記述する。
r_main.cのmain関数を以下のように記述

void main(void)
{
    R_MAIN_UserInit();
    /* Start user code. Do not edit comment generated here */
    R_RTC_Start();

    while (1U)
    {
        ;
    }
    /* End user code. Do not edit comment generated here */
}

次にr_cg_rtc_user.cの割り込み関数r_rtc_callback_constperiod()を以下のように記述。

static void r_rtc_callback_constperiod(void)
{
    /* Start user code. Do not edit comment generated here */
    static int flg = 0;
    
    if (flg) {
	    P7 = P7 & 0x7f;
	    flg = 0;
    } else {
	    P7 = P7 | 0x80;
	    flg = 1;
    }
    /* End user code. Do not edit comment generated here */
}

あとはこれをビルドし基板にダウンロード・実行すると、基板上のLEDが2秒周期で点滅するようになる。

TOPPERS/ASP3をシミュレータで動かす & タスクを実装する

最近公開されたTOPPERS/ASP3については、Mac OS Xでのシミュレーション環境がいち早く公開されている。今回はそのビルド・実行方法と、簡易的なタスクの実装方法についてまとめる。

シミュレータの実行

ダウンロード

以下から「Mac OS Xシミュレーション環境簡易パッケージ」をダウンロード、解凍
TOPPERSプロジェクト/ASP3カーネル

環境構築

ダウンロードフォルダ中の「doc/user.txt」に必要な環境が記述されている。
基本的に以前のTOPPERS/ASPと同じ環境を使う。これについては公式・非公式ともにわかりやすい解説が複数あるので、今回は割愛する。
ただ一点だけ、TOPPERS/ASPからの変更点として、コンフィギュレータがrubyスクリプトになっているので、rubyの実行環境が必要になる。

ビルド、実行

ダウンロードフォルダで以下を実行する。

mkdir OBJ
cd OBJ
ruby ../configure.rb -T macosx_xcode
make

するとOBJフォルダ内に「asp」という実行ファイルが生成される。これを実行するとサンプル・アプリケーションを実行するシミュレーション環境が動く。

タスクの実装

今回はデフォルトの環境に簡単なタスクを追加する。
具体的には「task_hoge」「task_highlevel_hoge」という2つのタスクを用意。task_hogeからより優先度の高いtask_highlevel_hogeをウェイクアップする処理を追加する。

タスク本体の実装

まずタスクの実装を用意。sampleフォルダのsample1.cに以下を記述

void task_hoge(intptr_t exinf)
{
    while (true) {
        slp_tsk();
        syslog(LOG_INFO, "hoge");
        wup_tsk(TASK_HIGH_HOGE);
        syslog(LOG_INFO, "hoge end");
    }
}

void task_highlevel_hoge(intptr_t exinf)
{
    while (true) {
        slp_tsk();
        syslog(LOG_INFO, "high hoge");
        syslog(LOG_INFO, "high hoge end");
    }
}

sample1.hに以下のプロトタイプ宣言を追加。

extern void task_hoge(intptr_t exinf);
extern void task_highlevel_hoge(intptr_t exinf);

コンフィギュレータによるタスク設定コード生成

sampleフォルダのsample1.cfgに以下の記述を追加

CRE_TSK(TASK_HOGE, { TA_NULL, 10, task_hoge, HIGH_PRIORITY, STACK_SIZE, NULL });
CRE_TSK(TASK_HIGH_HOGE, { TA_NULL, 11, task_highlevel_hoge, HIGH_PRIORITY - 1, STACK_SIZE, NULL });

冒頭で作成したOBJフォルダにて、ターミナルで以下を実行してタスク設定コードを更新する。

ruby ../configure.rb -T macosx_xcode

タスク呼び出し部分の実装

sample1.cのmain_task関数内のタスク起動部の記述に、以下のタスクを起動する記述を追加。

act_tsk(TASK_HOGE);
act_tsk(TASK_HIGH_HOGE);
wup_tsk(TASK_HOGE);

なお既存コードに今回の追加を行うとtask関数内で不正なメモリアクセスが発生するけれど、追加部分の実行に支障はないのでそのままとする(graph配列への参照部分にガード条件を追加すれば抑止できる)。

ビルド、実行

ターミナルで以下を実行。

make
./asp

するとアプリケーション起動時に以下のログ出力が追加で表示されるようになる。実装通り、「task_hogeが、より優先度の高いtask_highlevel_hogeを呼び出す→task_hogeに復帰する」という動作が行われていることがわかる。

(略)
System logging task is started.
Sample program starts (exinf = 0).
hoge
high hoge
high hoge end
hoge end
(略)

MacやLinuxでPICTを使う

組合せテストツールのPICTの解説で、表題についてよく聞かれるのでメモ。

PICTについては、専用のバイナリファイルで配布されていたこともあり、今までWindowsで使用されてきた。
ただ去年からGithubオープンソース化され、gccやclangで自由にビルドできるようになった。それに伴い、MacLinuxでもWindowsと同じぐらい手軽に利用できるようになっている。

あえて言うまでもないかもしれないが、導入方法は以下の通り。最近のgccやclangが使え、makeできる環境であれば、OSは問わない。

  1. https://github.com/Microsoft/pict」にて、Download ZIPからファイルダウンロード、解凍
  2. 解答したディレクトリで「make」実行。同ディレクトリにバイナリpictができる。

使用例

このディレクトリに、例えば以下のようなファイルsample.txtを作成する

OS:Win, Mac, Ubuntu
Compiler:GCC, clang
Lang:c, c++, asm

そこで以下のコマンドを実行する。

./pict sample.txt

すると以下のように2ワイズカバレッジ100%の組合せ一覧が出力される。

OS	Compiler	Lang
Win	GCC	asm
Ubuntu	clang	asm
Mac	GCC	c
Mac	clang	c++
Win	clang	c
Ubuntu	GCC	c++
Win	clang	c++
Ubuntu	clang	c
Mac	GCC	asm

clang/gccに組み込まれたAddressSanitizer/LeakSanitizerでメモリエラーを捕捉する

C/C++でのユニットテストによるメモリリーク検出 - 千里霧中の補足。

メモリエラーの検出方法についてだけれど、最近のclangやgccだと、AddressSanitizerという動的解析ツールが組み込まれており、それを活用できる。

使用する場合はコンパイラオプション「-fsanitize=address」「-fsanitize=leak」等を指定する。

題材

例えば以下のコードを対象にする。

//main.c
#include <stdio.h>
#include <stdlib.h>

void hoge(void)
{
        int *a_buff = (int *)malloc(5 * sizeof(int));
        a_buff[10] = 8;
}

int main(void)
{
        printf("test\n");
        hoge();
        return 0;
}


これを普通にコンパイルして実行すると「test」が表示されるだけで、特にエラーなどは検出されない。

AddressSanitizerでの不正なメモリアクセスの検出

一方で、以下のオプションでコンパイルして、AddressSanitizerを有効化する。

gcc -g -fsanitize=address main.c

これで実行すると、「a_buff[10] = 8」の実行タイミングで以下のエラーメッセージが出力されるようになる。

==23801==ERROR: AddressSanitizer: heap-buffer-overflow on address 
略
==23801==atos returned: An admin user name and password is required to enter Developer Mode.
    #0 0x113d63d4d in hoge (*****/./a.out+0x110000d4d)
    #1 0x113d63e04 in main (*****/./a.out+0x110000e04)
    #2 0x1ff199451ac in start (/usr/lib/system/libdyld.dylib+0x35a0)
    #3 0x0  (<unknown module>)
略
SUMMARY: AddressSanitizer: heap-buffer-overflow ??:0 hoge
Shadow bytes around the buggy address:
以下略

LeakSanitizerでのメモリリークの検出

次に以下のオプションでコンパイルして、LeakSanitizerを有効化する。

gcc -g -fsanitize=leak main.c

これを実行すると、アプリケーション終了時点で以下のエラーメッセージが出力されるようになる。

==6316==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 20 byte(s) in 1 object(s) allocated from:
略