高完整性系统工程(七):Safe Language Subsets, SPARK Ada

目录

1. 引言

2. SAFE LANGUAGE SUBSETS

2.1 Safe Languages 安全语言定义

2.2 Design Space 安全语言的设计

2.3 Safe Subsets 安全子集

2.3.1 定义和优点

2.3.2 NASA 的 C 子集

2.3.3 Language Compliance 语言合规性

2.3.4 Predictable Execution 可预测的执行

2.3.5 Defensive Coding 防御性编码

2.3.6 Code Clarity 代码清晰度

2.4 关于简洁性

2.5 SPARK 编程语言


1. 引言

下面的代码存在哪些问题?

int binarySearch(int [] list, int target) {
    int low = 0;
    int high = len(list) - 1;
    int mid;
    while(low <= high) {
        mid = (low + high)/2;
        if (list[mid] < target) {
            low = mid + 1;
        } else if (list[mid] > target) {
            high = mid - 1;
        } else {
            return mid;
        }
    }
    return -1;
}

上述代码是使用二分查找法搜索目标元素的简单实现。然而,从安全性的角度来看,存在一些潜在的问题:

  1. 数组边界检查:代码中没有对输入的数组list进行边界检查。如果传递给函数的数组是空的,那么high = len(list) - 1;将会得到-1,这可能会导致后续的比较出现问题。

  2. 整数溢出:在计算mid = (low + high)/2;时,如果lowhigh的值都非常大,那么low + high可能会超过int类型能表示的最大值,从而导致整数溢出。这个问题可以通过改变计算mid的方法来解决,如mid = low + ((high - low) / 2);

  3. 返回值的含义:当目标元素不在数组中时,函数返回-1。然而,如果使用函数返回值作为数组的索引,这可能会导致数组越界的错误。在使用返回值之前,调用者需要检查其是否为-1。

  4. 输入参数的验证:函数的参数target没有进行验证。虽然在这个具体的上下文中,对target进行验证可能没有太大的意义,但在一般的函数设计中,验证输入参数的有效性是一个很好的习惯。

2. SAFE LANGUAGE SUBSETS

2.1 Safe Languages 安全语言定义

  • 安全编程语言指的是那些能够保证程序行为与程序员意图一致的编程语言。这涉及到程序的可预测性和可验证性。
  • 安全语言并不总是意味着程序不会崩溃,但它意味着这些语言相对于不安全的语言来说更简单。

2.2 Design Space 安全语言的设计

  • 全新设计:设计一种全新的、安全的编程语言,例如 Rust。
  • 安全子集:使用现有语言的安全子集,移除所有不安全的部分,例如 SPARK Ada 和 MISRA C。

2.3 Safe Subsets 安全子集

2.3.1 定义和优点

  • 安全子集从一种语言中移除不安全的部分。
  • 它的哲学是让程序员付出更多努力,换取更简单的测试和验证过程。
  • 安全子集的程序可以使用现有的工具链进行编译和调试。
  • 程序员不需要学习新的编程语言。
  • 结果的程序更易于自动化分析和验证。

2.3.2 NASA 的 C 子集

  • NASA 使用了 MISRA C 的一个子集(MISRA C 本身就是 C 的子集)。
  • 这个子集包含了一系列的规则,涵盖了语言合规性、预测性执行、防御性编码和代码清晰度等方面。

2.3.3 Language Compliance 语言合规性

  • 不要偏离语言定义。
  • 启用所有警告进行编译;使用静态源代码分析器。

2.3.4 Predictable Execution 可预测的执行

  • 所有预计要结束的循环,使用可验证的循环边界。
  • 不要使用直接或间接递归。
  • 在任务初始化后,不要使用动态内存分配。
  • 使用 IPC 消息进行任务通信。
  • 不要使用任务延迟进行任务同步。
  • 对于共享数据对象,明确地转移写权限(所有权)。
  • 对使用信号量和锁进行限制。
  • 使用内存保护、安全边缘、屏障模式。
  • 不要使用 goto、setjmp 或 longjmp。
  • 不要对枚举列表的元素进行选择性值赋值。

2.3.5 Defensive Coding 防御性编码

  • 在尽可能小的范围内声明数据对象。
  • 检查非 void 函数的返回值,或显式地转换为 (void)。
  • 检查传递给函数的值的有效性。
  • 使用静态和动态断言作为完整性检查。
  • 使用 U32、I16 等,而不是预定义的 C 数据类型,如 int、short 等。
  • 显式地确定复合表达式中的计算顺序。
  • 不要使用有副作用的表达式。

2.3.6 Code Clarity 代码清晰度

  • 只在非常有限的情况下使用 C 预处理器。
  • 不要在函数或块内定义宏。
  • 不要取消定义或重新定义宏。
  • 将 #else、#elif 和 #endif 放在与匹配的 #if 或 #ifdef 相同的文件中。
  • 每行文字中最多只放置一条语句或声明。
  • 使用短函数,参数数量有限。
  • 每个声明最多使用两级间接引用。
  • 每个对象引用最多使用两级解引用。
  • 不要在宏或类型定义中隐藏解引用操作。
  • 不要使用非常量函数指针。
  • 不要将函数指针转换为其他类型。
  • 不要在 #include 指令前放置代码或声明。

目标是最大化代码的可移植性、可预测性和简单性。

2.4 关于简洁性

Tony Hoare 在 1980 年 ACM 图灵奖演讲中的一段话,强调了软件设计的两种方法:

  1. 设计得非常简单,以至于明显没有缺陷。
  2. 设计得非常复杂,以至于没有明显的缺陷。

这段话强调了简单性在软件设计中的重要性。复杂性虽然可以遮掩缺陷,但是在长远看来,简单的设计更易于理解、维护和测试。

2.5 SPARK 编程语言

SPARK 是以 Ada 为基础的一种编程语言,Ada 本身已经是相当安全的语言。在 SPARK 中,移除了一些可能导致安全问题的特性,如动态内存分配、任务(可以参考 Ravenscar Profile)、Goto、访问类型(指针)、有副作用的表达式和函数、异常处理等。

同时,SPARK 添加了注解(Annotations)功能,可以对代码进行检查,包括:

  • 代码是否符合 SPARK 的约束
  • 是否存在未使用的赋值
  • 是否存在使用了未初始化的数据
  • 是否存在缺少的返回语句
  • 是否存在违反流程约定的情况("Depends")

例如,一个 SPARK 程序包可以这样写:

package Swapping with
SPARK Mode => On
is
procedure Swap (X, Y : in out Float) with Depends => (x=>Y, Y=>x);
end Swapping;

此外,SPARK 还可以检查以下情况:

  • 使用了未初始化的数据
  • 可能的运行时错误
  • 函数契约("Pre/Post" 条件)

例如,你可以在 SPARK 中定义如下函数:

procedure Swap (X, Y : in out Integer) with Post => (X=Y'Old and Y = X'Old);
function Divide (X, Y : Integer) return Integer
with Pre => Y /= 0 and (if X = Integer'First then Y /= -1), Post => Divide'Result = X/Y;

在这两个例子中,Swap 函数在后置条件中明确了交换后 X 和 Y 的值,而 Divide 函数在前置条件中规定了 Y 不能为 0,且如果 X 是 Integer 的最小值,那么 Y 不能为 -1,后置条件则规定了返回值应该是 X 除以 Y。

这种方式大部分是通过示例(Demonstration)来学习和理解的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值