# 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"); } ```