はじめに

2019/2/10追記

記事を書いてから気づいたのですが、正確には、Rustのアトリビュートをいくつか設定すれば、MISRA-Cのルールを90%満足できるでした。

私はMISRA-Cのコーディング規約でプログラミングしたことがないため、内容に誤りがありかもしれません。間違っている点があれば、ご指摘いただけるとありがたいです。

後、いつも通りですが、C言語を貶める意図は一切ありません。

昨日からtwitterで、Rustが組込みのセキュリティが重要な分野で広まると良いなぁ、という議論がありました。 その中で、車載では、やはりMISRA基準との関係が明確になること、ということが1つの基準になりそうでした。

github.com

@hashaskell さんから、MISRAコーディングルールのうち、Rustコンパイラがアトリビュートの設定を含めて、保証するルールのリストを作成しているレポジトリを教えて頂きました。

結果のサマリを集計したところ、次のような結果でした。

status ルール数 デフォルトでコンパイラエラー 89 C言語特有機能のためのルールで、Rustに該当機能がないものが 54 デフォルトでコンパイルエラーにならない 35

つまり、Rustでコンパイルが通った時点で、MISRAコーディング基準の80%を満たしていることになります。

Rustを使い始めてわかったのは、C/C++では長い間かけてゆっくり学んでいたような「良い書き方」を学ばないことには、 Rustではプログラムをコンパイルすることすらできない、ということだ。 プログラミングRustより

残り35個のルールが気になったため、1つずつその内容を確認しました。

6個は、Rustコンパイラまたはclippyの警告で検出可能です。 11個は、Rustの言語仕様上、問題にならないと考えられます（私がそう考える、というだけですが）。

ということで、Rustのアトリビュートを設定した上で、コンパイルでき、コンパイラの警告とclippyの警告を修正すると、MISRAコーディング基準の90%は満たすことになります。 コンパイラの警告とclippyの警告を修正するのは、割と普通のRustプログラミングの範囲です。 どうしても黙らせたい時もありますが

と、いうことで、Rustやろうぜ！

本記事の趣旨は以上です。

以下、コンパイルエラーにできない35個のルールを1個ずつ見ていった結果です。 興味のある方はご覧ください。

MISRA-Rust

MISRA-Rust

デフォルトでコンパイルエラーにならない例

MISRA-Rules

MISRAのルールは下から引用しています。

MISRA-C

コメント

3.1 「/*」や「//」という文字の並びをコメント内で使用してはならない

どうでも良さそうですね。

fn main () { }

識別子

MISRAの基本方針が、「識別子を再利用しない」なので、Rustがこのあたりのルールに準拠しないのは当然です。 Rustはシャドーイングを許す言語ですが、これは次のような利点があります。

可変性の制御

中間変数

可変性の制御では、ある変数が変更不要になったことをコンパイラに教えることができます。

let mut x = 10 ; let x = x;

また、処理が連鎖するような場合、シャドーイングにより、誤って中間結果を使うことがなくなります。

let data = 0 ; let data = do_something (data); let data = do_another (data); let data = data. unwrap ();

シャドーイングする度に、その前の data にはアクセスできなくなります。 これは、下手に一時変数を作るより安全ではないでしょうか。

5.1 外部識別子は異なったものにしなければならない

テストコードがおかしい気がします。外部に公開する識別子が一意になれば良い気がしますが…？

const ABC: i32 = 0 ; fn main () { let abc: i32 = 1 ; let _ = abc + ABC; }

5.2 同じスコープと名前空間で宣言された識別子は異なったものにしなければならない

当然シャドーイングが発生します。

fn main () { let engine_exhaust_gas_temperature_raw: i32 = 0 ; let engine_exhaust_gas_temperature_raw: i32 = 1 ; }

5.3 内側のスコープで宣言された識別子は、外側のスコープで宣言された識別子を隠してはならない

はい。

fn main () { let i: i16 = 1 ; if true { let i: i16 = 0 ; let _ = i; } let _ = i; }

しかし、 static や const のシャドーイングは、コンパイルエラーになります。

static abc: i32 = 0 ; { let abc: i32 = 1 ; }

error[E0530]: let bindings cannot shadow statics | 4 | static abc: i32 = 0; | -------------------- the static `abc` is defined here 5 | { 6 | let abc: i32 = 1; | ^^^ cannot be named the same as a static

そのため、グローバル領域に宣言したstatic変数や定数については、シャドーイングするとコンパイルエラーになります。 関数ローカルでしかシャドーイング起きない（？）ので、けっこう安全な気がします。

5.4 マクロ識別子は異なったものにしなければならない

通るんですね。意外。ちゃんと unused macro definition の警告は出ます。

macro_rules! engine_exhaust_gas_temperature_raw { () => { 3 ; }; } macro_rules! engine_exhaust_gas_temperature_raw { () => { 4 ; }; } fn main () { let _ = engine_exhaust_gas_temperature_raw! (); let _ = engine_exhaust_gas_temperature_raw! (); }

5.6 typedef名は一意の識別子でなければならない

ルール的には、関連型とか全部ダメですよね…。

fn main () { type U8 = bool ; { type U8 = u8 ; } }

リテラルと定数

7.2 符号なしの型で表現されているすべての整数定数には「u」または「U」接尾語を適用しなければならない

fn main () { let compliant_unsigned: u32 = 1u32 ; let unsigned: u32 = 1 ; }

C言語と違って、下はちゃんとエラーになります。

let unsigned: u32 = - 1 ;

error[E0600]: cannot apply unary operator `-` to type `u32` | 2 | let unsigned: u32 = -1; | ^^ cannot apply unary operator `-` | = note: unsigned values cannot be negated

C言語は、下のコードはコンパイルが通っちゃうので大変です。

uint32_t u = - 1 ;

7.4 オブジェクトの型が「const修飾文字へのポインタ」でない限り、文字列リテラルをオブジェクトに代入してはならない

普通にできちゃいますわな。

fn main () { let mut _l = "string literal" ; }

宣言と定義

8.1 型は明示的に指定されなければならない

まぁ、型推論ありますからね…。ただ、危ない暗黙の型変換が起きないため、明示的に型を書くメリットがほとんどありません。

fn main () { let x; x = 1 ;

8.7 翻訳単位内での参照がただ1つである場合、関数やオブジェクトは外部リンケージを使用して定義してはいけない

これは、clippyさんでも警告が出なかったです。

pub const LIBRARY_GLOBAL: u32 = 0 ;

8.9 識別子が単一の関数内にのみ出現する場合、そのオブジェクトはブロックスコープで定義されなければならない

これも、clippyさんでも警告出ないですね。

const GLOBAL: u32 = 0 ; fn main () { let _x = GLOBAL + 1 ; }

8.13 ポインタは可能な限りconst修飾型を指さなければならない

これは、残りのコードで使っていなければ、 mut 外せるよ、の警告が出ます。

let mut x: Box < u8 > = Box :: new ( 8 );

実質的な型モデル

10.1 オペランドが不適切な実質的な型であってはならない

テストコードが変な気がします。MISRAは暗黙変換のことを言っていると思うのですが…。

let x: i32 = 0xFF ; let y = x << 2 ;

CERT Cの似た項目にある、下がちゃんと動けば良いのでは？ C言語では、 0x0a が期待値ですが、最初に暗黙変換されてint32_tあたりになる結果、 0xfa になります。

uint8_t port = 0x5a ; uint8_t result_8 = ( ~port ) >> 4 ;

Rustでは、これはちゃんと 0x0a になります。 4 が u8 に型推論されるため(？)ですかね。

let x: u8 = 0x5a ; let y = ( ! x ) >> 4 ;

10.8 複合式の値は異なる実質的な型分類やより広い実質的な型にキャストしてはならない

これもテストケースがおかしい気がしますね…。複合式の中で最終結果より大きい型にキャストされてはいけない、というルールっぽいのですが。

let x: u16 = 1 ; let y: u16 = 2 ; let _: u32 = (x + y) as u32 ;

下のようなコードが通らなければ良い？下はコンパイルエラーになります。

let x: u8 = 255 ; let y: u8 = (x + 1u16 ) as u8 ;

式

12.1 式の中の演算子の優先順位は明白でなければならない

下はコンパイルエラーにはなりません。clippyさんは () を付けなさい、と言ってくれます。

let x: usize = 1 ; if x >= 2 << 2 + 1 as usize { }

12.4 定数式の評価は、符号なし整数のラップアラウンドを引き起こしてはならない

へー、これ、コンパイル時はダメなんですね。

let u8a: u8 = 0 ; let _x = u8a - 10 ;

皆さんご存知の通り、実行時は定義されたパニックが発生します。

thread 'main' panicked at 'attempt to subtract with overflow', src/main.rs:3:14

副作用

13.2 式の値とその永続的な副作用は、すべての許可された評価順で同じでなければならない

Rustは式の評価順決まっていそうですが…？ 明示的に書いてあるドキュメントは見つかっていませんが、Document function argument evaluation order (or lack thereof)などを見ても、左から右へ評価される模様です。

そうでないと、ボローチェッカがうまくチェックできない気がします。

/// This function has a side effect. fn increment (x: &mut u8 ) -> &mut u8 { * x += 1 ; x } /// This function does not have a side effect. fn add (x: u8 , y: u8 ) -> u8 { x + y } fn main () { let mut x: u8 = 0 ; let _ = add ( * increment ( &mut x), x); }

13.5 &&や||の論理演算子の右側のオペランドは、永続的な副作用を含んではならない

&&や||は、短絡評価されるので、これはいけません。clippyさんも叱ってくれないですね。

/// This function has a side effect. fn not (x: &mut bool ) -> &mut bool { * x = ! * x; x } fn main () { let mut x: bool = true ; if * not ( &mut x) || * not ( &mut x) { } }

制御文の式

14.1 ループカウンタは、実質的に浮動小数点型を持ってはいけない

ループカウンタ扱い...なのかな？

let mut f: f64 = 0_f64 ; while f < 1.0 { f += 0.001_f64 ; }

14.2 forループは適正に定義されなければならない

コンパイルエラーにはなりませんが、この書き方の場合、ループの回数自体は変化しません。100回きっちり周ります。

let mut bound: u32 = 100 ; for i in 0 ..bound { bound -= i; }

clippyさんも以下の苦言を呈してくれます。

warning: attempt to mutate range bound within loop; note that the range of the loop is unchanged | 6 | bound -= i; | ^^^^^^^^^^

ただし、 bound が途中でoverflowするので、実行時に定義されたパニックが発生します。

thread 'main' panicked at 'attempt to subtract with overflow', src/main.rs:7:9

14.3 制御式は不変であってはならない

意外とこれ叱ってもらえないんですね…。

let a: i32 = 0 ; if (a < 10 ) && (a > 20 ) { }

制御フロー

15.5 関数は、その最後に1つだけの出口を持たなけらばならない

まぁ、これはね…。必須ルールではありませんし。リソース解放忘れが通常は発生しないので、Rustではガンガン早期リターンすれば良いと思います。

let x = 1 ; if x > 1 { return ; } if x < 1 { return ; }

15.7 すべてのif ... else if構文は、else文で終了しなければならない

clippyさんも反応なし。

fn main () { let x = 1 ; if x == 2 { let _ = 1 ; } else if x == 3 { let _ = 2 ; } }

switch文

16.6 すべてのswitch文は、少なくとも2つのスイッチ節を持たなければならない

clippyさんも反応なし。

let i = 1 ; match i { _ => {} }

16.7 スイッチ式は実質的にブール型を持ってはいけない

clippyさんから、if/else式にしなよ、とお叱りを受けます。

let i = true ; match i as bool { false => { let _ = 1 ; } _ => { let _ = 2 ; } }

関数

17.2 関数は、直接的または間接的に、自分自身を呼び出しはいけない

そうだった。再帰呼出し禁止なんですよね…。

17.7 void以外の戻り値の型を持つ関数が返す値は使用されなければならない

_ で捨てるコードを書けば良いだけなのですが、それに意味があるか、という話ではありますね。 Result型は使わないと警告出ます。

fn func (para1: u16 ) -> u16 { para1 } fn discarded (para2: u16 ) { func (para2); } fn main () { discarded ( 1 ); }

17.8 関数パラメータを変更してはいけない

Rustでこんなコード書く人居るのかな…という気持ちになりながらも、コンパイルエラーにはなりません。

fn paramod ( mut para: u16 ) -> u16 { para += 1 ; let _ = para; 1 } fn main () { paramod ( 1 ); }

ポインタと配列

18.5 2段階を超える入れ子のポインタを宣言してはいけない

これ、clippyさんの警告が面白いです。

fn nesting (p: &&&&&& [ u8 ; 10 ]) { let _ = **** p; } fn main () { let a = [ 5 ; 10 ]; nesting ( &&&&&& a); }

warning: this argument is passed by reference, but would be more efficient if passed by value | 1 | fn nesting(p: &&&&&&[u8; 10]) { | ^^^^^^^^^^^^^^ help: consider passing by value instead: `&&&&&[u8; 10]`

1個減らせ、とのことです。ちなみに & が1個になるまで、永遠に1個減らせ、と言われます。ウケる。

重なり合う記憶域

19.2 unionキーワードを使用してはならない

使えますが、 unsafe です。

union UnionA { f1: i16 , f2: i32 , } fn main () { let mut u = UnionA { f2: 0 }; unsafe { u.f1 = u.f2 as i16 }; }

前処理指令

20.1 #include指令に対しては、他の前処理指令やコメントのみが先行しうる

#include と違って、 use は順番に依存しないので、ちょっと話が別な気がします。

struct MyStruct { a: u32 , } fn func (_: MyStruct) {} use std :: fmt; impl fmt :: Display for MyStruct { fn fmt ( & self , f: &mut fmt :: Formatter) -> fmt :: Result { write! (f, "MyStruct{{ {} }}" , self .a) } } fn main () { let s = MyStruct { a: 10 }; func (s); }

20.2 「'」、「"」または「\」文字、「/*」または「//」文字列がヘッダファイル名に存在してはならない

これ処理できるんですね。そもそもヘッダファイルがないので…。

include! ( "../../include/_'_.rs" ); fn main () {}

標準ライブラリ

21.1 予約済み識別子や予約済みマクロ名に対して#defineや#undefを使用してはならない

println って予約済みマクロなんでしょうか…？

macro_rules! println { () => { 3 ; }; } fn main () { println! (); }

21.2 予約済み識別子またはマクロ名を宣言してはならない

21.1と同じです。

参考

ESCR Ver. 2.0