MISRA C 2012 标准浅析

MISRA(The Motor Industry Software Reliability Association),汽车工业软件可靠性联会; 1994年,英国成立。致力于协助汽车厂商开发安全可靠的软件的跨国协会,其成员包括:AB汽车电子、罗孚汽车、宾利汽车、福特汽车、捷豹汽车、路虎公司、Lotus公司、MIRA公司、Ricardo公司、TRW汽车电子、利兹大学和福特VISTEON汽车系统公司。

MISRA支持C语言标准的发展历程:

MISRA C 2012目前已经修订了多次,最近版本是AMD2,已经支持C11标准。MISRA主要是功能安全,而CERT C/C++则更多的关注安全威胁。MISRA C 2012:AMD2总计规则数175条,其中规则158条,指令17条。其中指令基本上是无法被SAST工具所检测分析的。175条规则中强制为15条、必要规则120条、建议规则40条。

MISRA标准中每一条规则有很多描述信息,包括类别、可判断性、支持语言标准、分析范围、规则编号、规则描述等。请见下图示意:

对于这些规则描述项说明如下:

标准分类:规则(Rule)和指令(Directive)之间的区别:

指令是一种描述性的指导规范,它无法提供执行符合性检查所需的完整描述。为了能够进行检查,需要给评测人员提供额外的信息,如设计文件或需求说明。

指令部分主要分为:实现、编译与构建、要求追踪、代码设计四个部分,共16条规范。

规则可以对相关要求提供完整的描述,评测人员或静态分析工具可以在不需要额外信息的情况下检查源代码是否符合对应规则。

指令:仅仅依靠源代码分析,无法对指令进行合规性判定,往往需要开发人员提供更多的信息,如设计文档和要求说明。静态代码分析工具可以判断代码符合指令,但对于代码不符合指令的情况,代码分析工具给出的结果可能不一致。

规则:仅仅依靠源代码分析,就可以对规则进行判断,不需要开发人员提供更多的信息,所有的静态代码检测工具都应具有对规则进行合规性判定的能力。

Category类别:

Mandatory guidelines 强制的:2012版增加的

声明符合Misra的C代码应遵守所有强制性准则-不允许偏差

Required guidelines必须的:

公司或项目可以选择将任何必须的准则视为强制性准则,不符合需要正式的偏差许可

Advisory guidelines建议的:

建议性的,不符合应被记录,但是无需正式的偏差许可

Decidability可判定性和Undecidable 不可判定性:

Decidable可判断的,总是能够在任何程序中使用工具确定代码是否合规,否则就是不可判定的

例如:

可判定的,规则11.3:取决于源指针和目标指针的类型

不可判定的,规则12.2:取决于移位运算符右侧操作数的值。

Analysis Scope分析范围

Single translation unit rule 单一编译单元

通过隔离分析每个编译单元中的源代码,可以可靠地进行验证的代码

System rules系统级

只能通过分析整个系统中的源代码了完全检查验证

MISRA合规对于企业的价值:

  1. MISRA是汽车行业公认的C/C++语言编码规范;
  2. MISRA和AUTOSAR已经联合,即将发布C++最佳行业实践标准;
  3. 符合编码规范是ISO 26262:2018-6软件功能安全开发标准的内在要求之一,开发ASIL功能安全项目必须满足;
  4. 客户软件过程能力审核要求;
  5. 在研发生命周期早期发现软件中的缺陷,预防成本投入会大幅度降低投产后的售后维护成本。

对于C、C++语言开发,我们关注里面包括的运行时缺陷,例如缓冲区溢出、整数溢出、数组越界、内存泄露、空指针解引用等缺陷。但是对于出海的企业,例如车企其开发软件遵守MISRA标准是必要的。这些规则虽然大多数不会导致程序崩溃,但是会给这些软件运行带来潜在的出错风险,对于可能造成重大生命财产随时的软件还是应该去遵守的。下面我们列举几个例子、说明代码不安全性主要来源。

  1. 开发者编码引入的错误;

     例如M6-2-1:赋值运算符不得用于子表达式;  a = b = c = 10;    if((x=y) != 0)

  1. 开发者对C/C++语言的误解;

      例如:M5-0-2: 表达式中的 C++ 运算符优先规则应受到有限的依赖

     x = ( a + b );       x = static_cast< uint16_t > ( a ) + b; 

  1. 编译器不执行开发人员所期望的操作;

     例如:规则 M4-5-3:类型(普通)char 和 wchar_t 的表达式不得用作除赋值运算符 =、相等运算符 == 和 ! 之外的内置运算符的操作数 = 和一元 & 运算符。

     if ( ( ch >= ‘a’ ) && ( ch <= ‘z’ ) )

  1. 编译器包含错误;

    例如: M1-0-2: (强制性,工具链,非自动化)仅当多个编译器具有通用的定义接口时,才应使用多个编译器;

如果一个模块要用C++以外的语言实现,或者使用不同的编译器编译,需确保这个模块的正确集成。强制性考虑的问题包括:堆栈使用、参数传递、数据值的存储方式(长度、对齐、混叠、叠加等)。

  1. 运行时错误;

 例如A18-0-2: 应检查从字符串到数值的转换的错误状态:

std::uint16_t a;     std::cin >> a;    // 没有检测到错误

目前,业界对于MISRA C 2012:ADM2支持最好的SAST工具是Coverity,支持的规则最多,检测效果最好。其次是北大Cobot,检测效果还可以。下面是MISRA C 2012规则全集,请参考。

分类子类编号类别规则项支持启用
指令实施/实现Dir 1.1必要程序输出所依赖的任何由实现定义的行为都应被记录和理解
编译与构建Dir 2.1必要所有源文件都应通过编译且没有任何编译错误
需求可追溯性 Dir 3.1必要所有代码应可追溯到书面要求
代码设计Dir 4.1必要所有运行时缺陷都最小化
Dir 4.2建议汇编语言的所有运用都应记录在案
Dir 4.3必要汇编语言应被封装和隔离
Dir 4.4建议代码段不应被“注释掉”
Dir 4.5建议具有相同可见性的相同名称空间中的标识符在印刷/屏幕显示上应明确
Dir 4.6建议应使用指示大小和符号的 typedef 类型代替基本数字类型
Dir 4.7必要如果函数返回错误信息,则应测试该错误信息
Dir 4.8建议如果一个指向结构体或联合体的指针在编译/解释时从未被反引用,则应隐藏该对象的实现
Dir 4.9建议应该优先使用函数,而不是类似函数的宏(如果它们可以互换)
Dir 4.10必要应采取预防措施以防止头文件的内容被多次包含
Dir 4.11必要应该检查传递给库函数的值的有效性。
Dir 4.12必要不得使用动态内存分配
Dir 4.13建议用于对资源进行操作的功能应按适当的顺序调用
Dir 4.14必要应该检查从外部源接收的值的有效性。 
规则标准 C 环境Rule 1.1必要程序不得违反标准 C 语法和约束,并且不得超出具体实现的编译限制
Rule 1.2建议不应该使用语言扩展
Rule 1.3必要不得发生未定义或严重的未指定行为
Rule 1.4必要不得使用不成熟的语言特性
未使用的代码Rule 2.1必要项目不能包含不可达代码
Rule 2.2建议不得有无效代码(dead code)
Rule 2.3建议项目不应包含未被使用的类型(type)声明
Rule 2.4建议项目不应包含未被使用的类型标签(tag)声明
Rule 2.5建议项目不应包含未被使用的宏(macro)声明
Rule 2.6建议函数不应包含未被使用的执行标签(label)声明
Rule 2.7建议函数中不应有未使用的变量
注释Rule 3.1必要字符序列注释“/*”和“//”(&#47;*与&#47;&#47;)不应出现在注释中
Rule 3.2必要“//”注释中不得使用换行(即“//”注释中不得使用行拼接符“\”)
字符集和词汇约定Rule 4.1必要八进制和十六进制转译序列应有明确的终止识别标识
Rule 4.2建议禁止使用三字母词(trigraphs)
标识符Rule 5.1必要外部标识符不得重名
Rule 5.2必要同范围和命名空间内的标识符不得重名
Rule 5.3必要内部声明的标识符不得隐藏外部声明的标识符
Rule 5.4必要宏标识符不得重名
Rule 5.5必要宏标识符与其他标识符不得重名
Rule 5.6必要typedef 名称应是唯一标识符
Rule 5.7必要标签(tag)名称应是唯一标识符
Rule 5.8必要全局(external linkage)对象和函数的标识符应是唯一的
Rule 5.9建议局部全局(internal linkage)对象和函数的标识符应是唯一的
类型Rule 6.1必要位域(位带)仅允许使用适当的类型来声明(位域成员类型限制)
Rule 6.2必要单比特(single-bit)位域成员不可声明为有符号类型
字符和常量Rule 7.1必要禁止使用八进制常数
Rule 7.2必要后缀“u”或“U”应使用于所有无符号的整数常量
Rule 7.3必要小写字符“l”不得作为常量的后缀使用(仅可使用“L”)
Rule 7.4必要除非对象的类型为“指向 const char 的指针”,否则不得将字符串常量赋值给该对象
声明和定义Rule 8.1必要类型须明确声明
Rule 8.2必要函数类型应为带有命名形参的原型形式
Rule 8.3必要对象或函数的所有声明均应使用相同的名称和类型限定符
Rule 8.4必要全局(external linkage)的对象和函数,应有显式的合规的声明
Rule 8.5必要全局对象或函数应在且只在一个文件中声明一次
Rule 8.6必要全局标识符应在且只在一处定义
Rule 8.7建议仅在本编译单元中调用的对象和函数,应定义成局部属性
Rule 8.8必要“static”修饰符应用在所有局部全局对象和局部函数(internal linkage)的声明中
Rule 8.9建议若一个对象的标识符仅在一个函数中出现,则应将它定义在块范围内
Rule 8.10必要内联函数应使用静态存储类声明
Rule 8.11建议声明具有外部链接的数组时,应明确指定其大小
Rule 8.12必要在枚举列表中,隐式指定的枚举常量的值应唯一
Rule 8.13建议指针应尽可能指向 const 限定类型
Rule 8.14必要不得使用类型限定符“restrict”
初始化Rule 9.1强制具有自动存储持续时间的对象(临时变量)的值在设置前不得读取
Rule 9.2必要集合或联合体的初始化应括在花括号“{}”中
Rule 9.3必要数组不得部分初始化
Rule 9.4必要数组的元素不得被初始化超过一次
Rule 9.5必要在使用指定初始化方式初始化数组对象的情况下,应明确指定数组的大小
基本类型模型 Rule 10.1必要操作数不得为不适当的基本类型
Rule 10.2必要字符类型的表达式不得在加减运算中使用不当
Rule 10.3必要表达式的值不得赋值给具有较窄基本类型或不同基本类型的对象
Rule 10.4必要执行常规算术转换的运算符的两个操作数应有相同的基本类型
Rule 10.5建议表达式的值不应(强制)转换为不适当的基本类型
Rule 10.6必要复合表达式的值不得赋值给具有较宽基本类型的对象
Rule 10.7必要如果将复合表达式用作执行常规算术转换的运算符的一个操作数,则另一个操作数不得具有更宽的基本类型
Rule 10.8必要 复合表达式的值不得转换为其他基本类型或更宽的基本类型
指针类型转换Rule 11.1必要不得在指向函数的指针和任何其他类型的指针之间进行转换
Rule 11.2必要不得在指向不完整类型的指针和其他任何类型间进行转换
Rule 11.3必要不得在指向不同对象类型的指针之间执行强制转换
Rule 11.4建议不得在指向对象的指针和整数类型之间进行转换
Rule 11.5建议不得将指向 void 的指针转换为指向对象的指针
Rule 11.6必要不得在指向 void 的指针和算术类型之间执行强制转换
Rule 11.7必要不得在指向对象的指针和非整数算术类型之间执行强制转换
Rule 11.8必要强制转换不得从指针指向的类型中删除任何 const 或 volatile 限定符
Rule 11.9必要宏“NULL”是整数型空指针常量的唯一允许形式
表达式Rule 12.1建议表达式中运算符的优先级应明确
Rule 12.2必要移位运算符的右操作数应在零到比左操作数基本类型的位宽度小一的范围内
Rule 12.3建议不得使用逗号(,)运算符
Rule 12.4建议常量表达式的求值不应导致无符号整数的回绕
Rule 12.5建议sizeof运算符不能有声明为“类型数组”的函数参数作为操作数
副作用Rule 13.1必要初始化程序列表不得包含持久性副作用
Rule 13.2必要在所有允许的评估顺序中表达式的值及其持久的其他作用应该保持相同。
Rule 13.3建议包含自增(++)或自减(--)运算符的完整表达式,除由自增或自减运算符引起的副作用外,不应有其他潜在的副作用
Rule 13.4建议不得使用赋值运算符的结果
Rule 13.5必要逻辑与(&&)和逻辑或(||)的右操作数不得含有持久性副作用
Rule 13.6强制sizeof 运算符的操作数不得包含任何可能产生副作用的表达
控制语句表达式Rule 14.1必要循环计数器的基本类型不能为浮点型
Rule 14.2必要 for 循环应为良好格式
Rule 14.3必要控制表达式不得是值不变的
Rule 14.4必要if 语句和循环语句的控制表达式的基本类型应为布尔型
控制流Rule 15.1建议不应使用 goto 语句
Rule 15.2必要goto 语句仅允许跳到在同一函数中声明的稍后位置的标签
Rule 15.3必要goto语句引用的标签必须在goto语句所在代码块或包含该代码块的上级代码块中声明
Rule 15.4建议最多只能有一个用于终止循环语句的 break 或 goto 语句
Rule 15.5建议应仅在函数的末尾有单个函数出口
Rule 15.6必要循环语句和选择语句的主体应为复合语句
Rule 15.7必要所有的 if…else if 构造都应以 else 语句结束
Switch 语句Rule 16.1必要switch 语句应格式正确
Rule 16.2必要switch 标签只能出现在构成 switch 语句主体的复合语句的最外层
Rule 16.3必要每一个 switch 子句(switch-clause)都应以无条件 break 语句终止
Rule 16.4必要每个 switch 语句都应具有 default 标签
Rule 16.5必要Default 标签应作为 switch 语句的第一个或最后一个 switch 标签
Rule 16.6必要每个 switch 语句应至少有两个 switch 子句
Rule 16.7必要switch 语句的控制表达式(switch-expression)的基本类型不得是布尔型
函数Rule 17.1必要不得使用<stdarg.h>的功能
Rule 17.2必要函数不得直接或间接调用自身(不得使用递归函数)
Rule 17.3强制禁止隐式声明函数
Rule 17.4强制具有非 void 返回类型的函数的所有退出路径都应为具有带有表达式的显式return 语句
Rule 17.5建议与数组型函数形参对应的函数入参应具有适当数量的元素
Rule 17.6强制数组形参的声明不得在[]之间包含 static 关键字
Rule 17.7必要非 void 返回类型的函数的返回值应该被使用
Rule 17.8建议不应更改函数形参
指针和数组Rule 18.1必要指针操作数的算术运算应仅用于寻址与该指针操作数相同数组的元素
Rule 18.2必要指针之间的减法应仅用于寻址同一数组元素的指针
Rule 18.3必要关系运算符>,> =,<和<=不得应用于指针类型的对象,除非它们指向同一对象
Rule 18.4建议“+,-,+=”和“-=”运算符不得应用于指针类型的表达式
Rule 18.5建议声明中最多包含两层指针嵌套
Rule 18.6必要具有自动存储功能的对象的地址不得复制给在它的生命周期结束后仍会存在的另一个对象
Rule 18.7必要不得声明可变的数组成员
Rule 18.8必要不得使用可变长数组类型
重叠存储Rule 19.1强制不得将对象赋值或复制给重叠的对象
Rule 19.2必要不得使用 union 关键字
预处理指令Rule 20.1建议#include 指令之前仅允许出现预处理指令或注释
Rule 20.2必要头文件名中不得出现“'”、“"”、“\”、字符以及“/*”或“//”字符序列
Rule 20.3必要#include 指令后须跟随<filename>或"filename"序列
Rule 20.4必要宏不得与关键字同名
Rule 20.5建议不应使用#undef
Rule 20.6必要看起来像预处理指令的符号不得出现在宏参数内
Rule 20.7必要宏参数展开产生的表达式应放在括号内
Rule 20.8必要#if 或#elif 预处理指令的控制表达式的计算结果应为 0 或 1
Rule 20.9必要#if 或#elif 预处理指令的控制表达式中使用的所有标识符应在其评估前被#define 定义
Rule 20.10建议不应使用“#”和“##”预处理运算符
Rule 20.11必要紧跟在“#”运算符之后的宏参数后面不得紧随“##”运算符
Rule 20.12必要用作“#”或“##”运算符的操作数的宏参数,不得是本身需要进一步宏替换的操作数
Rule 20.13必要以“#”作为第一个字符的一行代码应为有效的预处理指令
Rule 20.14必要所有#else,#elif 和#endif 预处理程序指令都应和与其相关的#if,#ifdef 或#ifndef 指令位于同一文件中
标准库Rule 21.1必要不得将#define 和#undef 用于保留的标识符或保留的宏名称
Rule 21.2必要不得声明保留的标识符或宏名称
Rule 21.3必要不得使用<stdlib.h>中的内存分配和释放函数
Rule 21.4必要不得使用标准头文件<setjmp.h>
Rule 21.5必要不得使用标准头文件<signal.h>
Rule 21.6必要不得使用标准库输入/输出函数
Rule 21.7必要不得使用<stdlib.h>中的 atof、atoi、atol 和 atoll 函数
Rule 21.8必要不得使用<stdlib.h>中的 abort, exit, getenv 和 system 函数
Rule 21.9必要不得使用<stdlib.h>中的 bsearch 和 qsort 函数
Rule 21.10必要不得使用标准库时间和日期功能
Rule 21.11必要不得使用标准头文件<tgmath.h>
Rule 21.12建议不得使用<fenv.h>的异常处理功能
Rule 21.13强制在 <ctype.h> 中传递给函数的任何值都应表示为 unsigned char或者值 EOF。
Rule 21.14必要不应使用标准库函数 memcmp 与以 null 终止的字符串进行比 较。
Rule 21.15必要标准库函数 memcpy、memmove 和 memcmp 的指针参数应该是指向兼容类型的合格或不合格版本的指针。
Rule 21.16必要标准库函数 memcmp 的指针参数应该指向指针类型、本质上signed 类型、本质上 unsigned 类型、本质上 Boolean 类型或者本质上 enum类型。
Rule 21.17强制使用来自 <string.h> 中的字符串处理函数不应导致访问超出它们的指针参数引用的对象的范围。
Rule 21.18强制传递给 <string.h> 中的任何函数的 size_t 参数都应有一个适当的值。
Rule 21.19强制只应将标准库函数 localeconv、getenv、setlocale 或 strerror返回的指针用作它们好像具有指向 const 限定类型的指针。
Rule 21.20强制标准库函数 asctime、ctime、gmtime、localtime、localeconv、getenv、setlocale 或 strerror 返回的指针不应后接调用相同函数的后续调用。
Rule 21.21必要不应使用 <stdlib.h> 的标准库函数 system。 
资源Rule 22.1必要通过标准库功能动态获取的所有资源均应明确释放
Rule 22.2强制只有通过标准库函数分配的内存块才能释放
Rule 22.3必要不得在不同的数据流上同时打开同一文件以进行读写访问
Rule 22.4强制禁止尝试对以只读方式打开的流执行写操作
Rule 22.5强制禁止反引用指向 FILE 对象的指针
Rule 22.6强制关联的流关闭后,禁止再使用指向 FILE 的指针值
Rule 22.7必要宏 EOF 只应与能够返回 EOF 的任何标准库函数的未修改返回值进行比较。
Rule 22.8必要在调用 errno-setting-function 之前,应将 errno 的值设置为零。
Rule 22.9必要在调用 errno-setting-function 之后,应测试 errno 的值是否为零。
Rule 22.10必要仅当要调用的最后一个函数为 errno-setting-function 时,才应测试 errno 的值。

(结束)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

manok

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值