# MISRA-C Rule 16 Switch statements
###### tags: `GNU GCC` `MISRA-C` `checker` `GNU GDB`
> This Page is part of our MISRA-C Rule checker
[ToC]
## :memo: MISRA-C 16 switch
### Rule 16.1 All switch statements shall be well-formed
### Rule 16.2 A switch label shall only be used when the most closely-enclosing compound statement is the body of a switch statement
每個switch都會有一個綁定的scope區間,內部的case綁定scope指向這個switch空間,所以如果一個case綁定的空間並非switch空間即可得知他另外綁定在了一個block之中。
- 修改位置 gcc-source 為您的開發版本 ex: gcc-7.5.0
```c
gcc-source/gcc/c/c-decl.c
bool
c_check_switch_jump_warnings (struct c_spot_bindings *switch_bindings,
location_t switch_loc, location_t case_loc)
```
- 規則描述
```c=
switch ( x )
{
case 1: /* Compliant */
if ( flag )
{
case 2: /* Non-compliant */
x = 1;
}
break;
default:
break;
}
```
- 解決方法
```c=
if (current_scope != switch_bindings->scope && Wmisra_c_trigger) {
if (current_scope->depth > switch_bindings->scope->depth + 2) {
inform(case_loc, "Misra-C Rule 16.2\n");
}
}
```
### Rule 16.3 An unconditional break statement shall terminate every switch-clause
- 修改位置
```c=
gcc-source/gcc/gimplify.c
static tree
warn_implicit_fallthrough_r (gimple_stmt_iterator *gsi_p, bool *handled_ops_p,
struct walk_stmt_info *)
```
這個規則可以透過gcc內部檢測參數warn_implicit_fallthrough來做偵測,因此我們只需要跟隨其內部檢測路徑加入Wmisra作為分支判斷即可。
這邊要注意的是,gcc的內部檢測通常會有前置的參數判斷來決定之後的function執行路徑,以下為攔截最終輸出結果function的backtrace。
```c=
(gdb) bt
#0 warn_implicit_fallthrough_r (gsi_p=gsi_p@entry=0x7fffffffd430, handled_ops_p=handled_ops_p@entry=0x7fffffffd3ff)
at ../../../gcc-7.5.0/gcc/gimplify.c:2113
#1 0x000000000082fb67 in walk_gimple_stmt (gsi=gsi@entry=0x7fffffffd430,
callback_stmt=callback_stmt@entry=0x831cb0 <warn_implicit_fallthrough_r(gimple_stmt_iterator*, bool*, walk_stmt_info*)>,
callback_op=callback_op@entry=0x0, wi=wi@entry=0x7fffffffd4a0) at ../../../gcc-7.5.0/gcc/gimple-walk.c:568
#2 0x000000000082fd41 in walk_gimple_seq_mod (pseq=<optimized out>,
callback_stmt=0x831cb0 <warn_implicit_fallthrough_r(gimple_stmt_iterator*, bool*, walk_stmt_info*)>, callback_op=0x0, wi=0x7fffffffd4a0)
at ../../../gcc-7.5.0/gcc/gimple-walk.c:51
#3 0x000000000082fdc7 in walk_gimple_seq (seq=seq@entry=0x7fffea2dc230,
callback_stmt=callback_stmt@entry=0x831cb0 <warn_implicit_fallthrough_r(gimple_stmt_iterator*, bool*, walk_stmt_info*)>,
callback_op=callback_op@entry=0x0, wi=wi@entry=0x7fffffffd4a0) at ../../../gcc-7.5.0/gcc/gimple-walk.c:81
#4 0x0000000000841c34 in maybe_warn_implicit_fallthrough (seq=<optimized out>) at ../../../gcc-7.5.0/gcc/gimplify.c:2154
#5 gimplify_switch_expr (pre_p=pre_p@entry=0x7fffffffd930, expr_p=<optimized out>) at ../../../gcc-7.5.0/gcc/gimplify.c:2300
#6 0x000000000083bab3 in gimplify_expr (expr_p=<optimized out>, pre_p=pre_p@entry=0x7fffffffd930, post_p=<optimized out>, post_p@entry=0x0,
gimple_test_f=gimple_test_f@entry=0x830d40 <is_gimple_stmt(tree)>, fallback=fallback@entry=0) at ../../../gcc-7.5.0/gcc/gimplify.c:11534
#7 0x000000000083dd79 in gimplify_stmt (stmt_p=<optimized out>, seq_p=seq_p@entry=0x7fffffffd930) at ../../../gcc-7.5.0/gcc/gimplify.c:6529
#8 0x000000000083bf9c in gimplify_statement_list (pre_p=0x7fffffffd930, expr_p=0x7fffea460e60) at ../../../gcc-7.5.0/gcc/gimplify.c:1721
#9 gimplify_expr (expr_p=<optimized out>, pre_p=pre_p@entry=0x7fffffffd930, post_p=<optimized out>, post_p@entry=0x0,
gimple_test_f=gimple_test_f@entry=0x830d40 <is_gimple_stmt(tree)>, fallback=fallback@entry=0) at ../../../gcc-7.5.0/gcc/gimplify.c:11754
#10 0x000000000083dd79 in gimplify_stmt (stmt_p=stmt_p@entry=0x7fffea460e60, seq_p=seq_p@entry=0x7fffffffd930)
at ../../../gcc-7.5.0/gcc/gimplify.c:6529
#11 0x000000000083e6a9 in gimplify_bind_expr (expr_p=expr_p@entry=0x7fffea453cc0, pre_p=pre_p@entry=0x7fffffffdb98)
at ../../../gcc-7.5.0/gcc/gimplify.c:1294
#12 0x000000000083c31b in gimplify_expr (expr_p=<optimized out>, pre_p=pre_p@entry=0x7fffffffdb98, post_p=<optimized out>, post_p@entry=0x0,
gimple_test_f=gimple_test_f@entry=0x830d40 <is_gimple_stmt(tree)>, fallback=fallback@entry=0) at ../../../gcc-7.5.0/gcc/gimplify.c:11526
#13 0x000000000083dd79 in gimplify_stmt (stmt_p=stmt_p@entry=0x7fffea453cc0, seq_p=seq_p@entry=0x7fffffffdb98)
at ../../../gcc-7.5.0/gcc/gimplify.c:6529
#14 0x000000000083f0f8 in gimplify_body (fndecl=fndecl@entry=0x7fffea453c00, do_parms=do_parms@entry=true)
at ../../../gcc-7.5.0/gcc/gimplify.c:12523
#15 0x000000000083f4a6 in gimplify_function_tree (fndecl=fndecl@entry=0x7fffea453c00) at ../../../gcc-7.5.0/gcc/gimplify.c:12681
#16 0x000000000070a9b0 in cgraph_node::analyze (this=this@entry=0x7fffea419450) at ../../../gcc-7.5.0/gcc/cgraphunit.c:657
#17 0x000000000070d1a8 in analyze_functions (first_time=first_time@entry=true) at ../../../gcc-7.5.0/gcc/cgraphunit.c:1118
#18 0x000000000070dba3 in symbol_table::finalize_compilation_unit (this=0x7fffea2d3100) at ../../../gcc-7.5.0/gcc/cgraphunit.c:2604
#19 0x0000000000a3ffd3 in compile_file () at ../../../gcc-7.5.0/gcc/toplev.c:492
```
我們這邊透過frame一層一層向下切換,最終在frame 4 找到進入解析的路徑點如下:
```c=
static void
maybe_warn_implicit_fallthrough (gimple_seq seq)
{
if (!warn_implicit_fallthrough)
return;
...
```
function開頭會先對是否有啟用相關參數做檢測來決定之後的執行路徑為何? 因此我們可以在這邊加入Wmisra作為條件分支。
- 規則描述
```c=
switch ( x )
{
case 0:
break; /* Compliant - unconditional break */
case 1:
case 2: /* Compliant - empty fall through */
break;
case 4:
a = b; /* Non-compliant - break omitted */
case 5:
if ( a == b )
{
++a;
break; /* Non-compliant - conditional break */
}
default:
; /* Non-compliant - default must also have a break */
}
```
### Rule 16.4 Every switch statement shall have a default label
我們增加一個變數misra_default_16_4來攔截一個switch解析之中是否包含default
根據C語言的語法定義,一個 label 具有以下形式
```c
label:
identifier : attributes[opt]
case constant-expression :
default :
```
並在finish case label之前檢查是否為真,如果為假我們將在該switch語句的block區塊結尾提示錯誤,並告知定義該switch語句的位置。
- 修改位置 gcc-source 為您的開發版本 ex: gcc-7.5.0
```c
gcc-source/gcc/c/c-parser.c
static void
c_parser_switch_statement (c_parser *parser, bool *if_p)
```
- 規則描述
```c
switch ( x )
{
case 0:
++x;
break;
case 1:
case 2:
break;
/* Non-compliant - default label is required */
}
```
- 解決方法
```c=
// 開始解析一個switch語句
c_start_case (switch_loc, switch_cond_loc, expr, explicit_cast_p);
...
body = c_parser_c99_block_statement (parser, if_p);
if (!misra_default_16_4 && Wmisra_c_trigger) {
inform(parser->tokens->location, "Misra-C Rule 16.4\n");
inform(switch_loc, "The switch is declaration in here\n");
}
misra_default_16_4 = 0;
c_finish_case (body, ce.original_type);
```
### Rule 16.5 A default label shall appear as either the first or the last switch label of a switch statement
因為規則要求default只能出現在switch scope的首尾(開始及結尾),所以我們創建一個 array 來保存一個 switch scope 內的全部label,並講他們依次推入陣列中。
格式為 [ (case | default) ] [ (location) ],當一個switch解析完畢後讀取陣列找出違規的報錯即可。
- 修改位置
```c
gcc-source/gcc/c/c-parser.c
// 解析一個 switch , 此處為判斷結果
static void
c_parser_switch_statement (c_parser *parser, bool *if_p)
// 此處為攔截case default標籤
static void
c_parser_label (c_parser *parser)
```
- 規則描述
```c=
switch ( x )
{
default: /* Compliant - default is the first label */
case 0:
++x;
break;
case 1:
case 2:
break;
}
switch ( x )
{
case 0:
++x;
break;
default: /* Non-compliant - default is mixed with the case labels */
x = 0;
break;
case 1:
case 2:
break;
}
```
- 解決方法
```c=
c_parser_next_token_is_keyword (parser, RID_CASE)
misra_array_16_5[misra_index_16_5++] = 1;
misra_array_16_5[misra_index_16_5++] = (int)parser->tokens->location;
c_parser_next_token_is_keyword (parser, RID_DEFAULT)
misra_array_16_5[misra_index_16_5++] = 2;
misra_array_16_5[misra_index_16_5++] = (int)parser->tokens->location;
if (Wmisra_c_trigger && misra_index_16_5) {
for (int i = 0; i < misra_index_16_5; i += 2) {
if (misra_array_16_5[i] == 2 && !i || misra_array_16_5[i] == 2 && i == misra_index_16_5 - 2) {
break;
} else if (misra_array_16_5[i] == 2) {
inform(misra_array_16_5[i + 1], "Misra-C Rule 16.5\n");
inform(switch_loc, "The switch is declaration in here\n")
break;
}
}
for (int i = 0; i < misra_index_16_5; i++) {
misra_array_16_5[i] = 0;
}
misra_index_16_5 = 0;
}
```
### Rule 16.6 Every switch statement shall have at least two switch-clauses
規則要求一個switch block應該至少包含兩個以上的結構標籤。
由於在上一個Rule中我們已經將一個switch內的所有的標間推入array。
因此我們只需從中撈出相應的標籤為址,並檢查一個標籤是否為一個完整的結構。
- 修改位置
```c
gcc-source/gcc/c/c-parser.c
static void
c_parser_switch_statement (c_parser *parser, bool *if_p)
static void
c_parser_label (c_parser *parser)
```
- 規則描述
```c=
switch ( x )
{
default: /* Non-compliant */
x = x + 1;
break;
}
switch ( y )
{
case 1:
default: /* Non-compliant */
y = y + 1;
break;
}
switch ( z )
{
case 1 :
z = 2;
break;
default : /* Compliant */
z = 0;
break;
}
```
- 解決方法
```c=
if (misra_index_16_5 < 4) {
inform(misra_array_16_5[misra_index_16_5 - 1], "Misra-C Rule 16.6\n");
inform(switch_loc, "The switch is declaration in here\n");
}
```
### Rule 16.7 A switch-expression shall not have essentially Boolean type
規則提出在一個switch condition裏頭包含boolean type可能會模糊化
其語意結構,在boolean type的判斷式中,使用if-else會是更好的選擇。
由於除了一個變數原先就具有bool的型態,還必須要考慮到其形態有可能會在
一個expression的運算中被改變,因此我們要找的是當expression結束時,他最終帶有的資料型態。
- 修改位置
```c
gcc-source/gcc/c/c-typeck.c
tree
c_start_case (location_t switch_loc,
location_t switch_cond_loc,
tree exp, bool explicit_cast_p)
```
- 規則描述
```c=
int x;
switch ( x == 0 ) /* Non-compliant */
{
case false:
y = x;
break;
default:
y = z;
break;
}
```
- 解決方法
```c=
#define TREE_TYPE(NODE) \
(CONTAINS_STRUCT_CHECK (NODE, TS_TYPED)->typed.type)
#define TREE_CODE(NODE) ((enum tree_code) (NODE)->base.code)
#define TREE_CODE_CLASS(CODE) tree_code_type[(int) (CODE)]
static inline bool
truth_value_p (enum tree_code code)
{
return (TREE_CODE_CLASS (code) == tcc_comparison
|| code == TRUTH_AND_EXPR || code == TRUTH_ANDIF_EXPR
|| code == TRUTH_OR_EXPR || code == TRUTH_ORIF_EXPR
|| code == TRUTH_XOR_EXPR || code == TRUTH_NOT_EXPR);
}
if ((TREE_CODE (type) == BOOLEAN_TYPE
|| truth_value_p (TREE_CODE (e)))
&& !(TREE_CODE (type) == INTEGER_TYPE
&& explicit_cast_p))
bool_cond_p = true;
if (bool_cond_p && Wmisra_c_trigger) {
inform(switch_cond_loc, "Misra-C Rule 16.7\n");
}
```