什么是安全的编程语言?


55

安全编程语言(PL)越来越流行。我想知道安全PL 的正式定义是什么。例如,C是不安全的,但是Java是安全的。我怀疑应该将“安全”属性应用于PL实施,而不是PL本身。如果是这样,让我们​​讨论安全PL实现的定义。我自己尝试将该概念形式化的结果很奇怪,所以我想听听其他意见。请不要说每个PL都有不安全的命令。我们总是可以选择一个安全的子集。


评论不作进一步讨论;此对话已转移至聊天
吉尔斯(Gillles)“所以-别再邪恶了” '18

“我们始终可以获取一个安全的子集”如何确定所生成的语言仍是图灵完备的?(这通常是“编程语言”的意思)
effeffe

“属性“安全”应应用于PL实现,而不是PL本身”-如果存在PL安全,则可以将其称为安全。
德米特里·格里戈列耶夫

Answers:


17

当我们在某种程度上将一种语言称为“安全” ,这正式意味着有证据表明,该语言的格式正确的程序无法做一些我们认为危险的事情。“安全”一词的使用也没有那么正式,但这就是这里的人们理解您的意思的意思。我们希望“安全”语言具有多种不同的属性定义。

一些重要的是:

  • Andrew Wright和Matthias Felleisen对“类型安全性”的定义在许多地方(包括Wikipedia)被引用为“类型安全性”的公认定义,并在1994年证明ML的一部分满足它。

  • Michael Hicks 在此处列出了“内存安全”的几个定义。有些是无法发生的错误类型的列表,有些是基于将指针视为功能的。Java unsafe通过让垃圾回收器管理所有分配和释放来保证所有这些错误都是不可能的(除非您显式使用标记为的功能)。Rust unsafe通过其仿射类型系统做出同样的保证(再次,除非您将代码明确标记为),该系统要求变量必须拥有或借用,才能使用一次。

  • 同样,线程安全代码通常被定义为不能显示涉及线程和共享内存的某些类型的错误的代码,包括数据争用和死锁。这些属性通常在语言级别上强制执行:Rust保证在其类型系统中不会发生数据争用,C ++保证其std::shared_ptr指向多个线程中相同对象的智能指针不会过早删除对象或在最后一次引用时无法删除它为了销毁它,C和C ++另外atomic在语言中内置了变量,如果正确使用,原子操作可以保证强制执行某些类型的内存一致性。MPI将进程间通信限制为显式消息,并且OpenMP具有语法以确保从不同线程访问变量是安全的。

  • 内存永不泄漏的属性通常称为空间安全。自动垃圾收集是确保这一点的一种语言功能。

  • 许多语言都保证其操作将获得明确的结果,并且其程序将具有良好的行为习惯。正如supercat给出的上述示例所示,C这样做是为了执行无符号算术(保证可以安全地环绕),而不是执行有符号算术(允许溢出导致任意错误,因为C需要支持在执行有符号算术时执行完全不同的操作的CPU)溢出),但随后该语言有时会默默地将未签名的数量转换为已签名的数量。

  • 函数式语言具有许多不变式,可以保证任何格式正确的程序都可以保持不变式,例如,纯函数不会引起副作用。这些可能会或可能不会被描述为“安全”。

  • 某些语言(例如SPARK或OCaml)旨在帮助证明程序的正确性。这可能被描述为“安全”,也可能不会被描述为“安全”。

  • 系统不能违反正式安全模型的证明(因此打趣说:“任何可证明是安全的系统可能都不是。”)


1
这可能被描述为“安全”,也可能不会被描述为“安全”。您能详细说明一下吗?“来自错误”是什么意思?
scaaahu

2
@scaaahu这是一个网站的示例,该网站将正式证明正确的软件称为“可证明的安全性”。 在这种情况下,它是指可防止飞机碰撞的软件,因此,它可以防止碰撞。
戴维斯洛

1
我接受此答案,因为它列出了安全类型。我想到的是内存安全性。
beroal

尽管此答案列出了一些有用的链接和许多示例,但大多数都完全弄糟了。垃圾回收可确保内存永远不会泄漏或不使用“不安全”块,从而自动为您提供安全性或有符号溢出的未定义行为,因为С编译器需要认真地支持某些怪异的CPU吗?对于Ada / SPARK,这只是一个简短的词,它是上述提到的唯一一种认真对待安全性的语言。
VTT

94

没有“安全编程语言”的正式定义;这是一个非正式的概念。相反,声称提供安全性的语言通常会提供关于要声明/保证/提供哪种安全性的准确的正式声明。例如,该语言可能提供类型安全性,内存安全性或某些其他类似的保证。


13
作为补充,如果我们像OP的帖子一样谈论C与Java的比较,那就是用Java而不是C来保证内存安全。这两种方式都是以自己的方式提供的。(是的,很多阅读此书的人已经知道了,但也许有些人不知道)。
Walfrat

17
@Walfrat这就是其中的一部分。Java也没有未定义的行为,这是我们期望的一种自称为“安全”的语言。对于类型系统,我不认为人们倾向于用“安全”来指代强大的静态类型系统。毕竟,像Python这样的动态类型语言毕竟是“安全的”。
Max Barraclough

2
我对类型安全性的定义是处理该问题的编译检查。那可能不是正式的定义。请注意,我说的是“类型安全”,而不是“安全”。对我来说,“安全”语言是指“我”对“类型和内存安全”的定义,我认为它可能是最广泛使用的一种。当然,我不是在谈论编译无法处理的一些陷阱,例如C中的反射/无效指针。安全的另一种可能定义是不会因段错误而崩溃的程序,例如C中的统一指针。类似的事情通常在Python和Java中被允许。
Walfrat

7
@Walfrat能让您轻松使用的是语法定义明确的语言。它不能保证执行定义正确-以及我看到JRE崩溃的次数,我可以告诉你,作为一个系统,它并不“安全”。另一方面,在C语言中,MISRA致力于避免未定义的行为,以获得更安全的语言子集,并且将C语言编译为汇编语言的定义更好。因此,这确实取决于您认为“安全”的内容。
格雷厄姆

5
@MaxBarraclough-“ Java也没有未定义的行为”-Java在语言定义的C规范中没有Java的未定义行为(尽管它确实允许某些代码产生没有单个预定义值的值,例如访问在另一个线程中被修改的变量,或者通过在另一个线程中被修改时访问double或在另一个线程中被修改的变量,long不能保证不会产生一个值的一半以某种未指定的方式与另一个值的一半混合),而是API规范但是在某些情况下具有不确定的行为。
Jules '18

42

如果您可以从本杰明·皮尔斯(Benjamin Pierce)的《类型和编程语言》Types and Programming Languages)的副本中获得经验,那么他对“安全语言”一词的各种观点都有很好的概述。

对于您可能会发现有趣的术语,一种建议的解释是:

“一种安全的语言完全由其程序员手册定义。”让一种语言的定义成为程序员需要理解的一组事物,以便预测该语言中每个程序的行为。然后,诸如C之类的语言的手册并未构成定义,因为某些程序(例如,涉及未经检查的数组访问或指针算术的程序)的行为无法在不知道特定C编译器如何在内存中布局结构的细节的情况下进行预测等等,并且当由不同的编译器执行时,同一程序可能具有完全不同的行为。

因此,我会犹豫使用术语“不安全”来指代编程语言实现。如果一种语言中未定义的术语在不同的实现中产生不同的行为,则其中一个实现可能会产生可能更令人期待的行为,但我不会将其称为“安全”。


7
停止问题当然是说,无论使用哪种语言,总会有一些程序的行为无法通过语言定义来预测。因此,任何依赖于“预测语言中每个程序的行为”的定义对于任何图灵完备的语言都存在根本性缺陷。
MSalters

15
@MSalters这是对暂停问题的普遍误解。暂停问题的不确定性意味着不可能以图灵完备的语言机械推导任意程序的行为。但是对于任何给定的程序来说,行为都是可以预期的。只是您无法制作做出此预测的计算机程序
吉尔斯(Gillles)“所以-别再作恶了”

7
@吉尔斯:事实并非如此。假设每个非终止程序都有一个非终止证明。然后,您可以枚举未终止的证明,以查找给定程序是否暂停。因此,暂停问题是可以确定的。矛盾。因此,某些非终止程序不是可证明的非终止程序。
凯文

9
@吉尔斯:我完全意识到许多程序被证明可以停止或不停止的事实。但是这里的声明实际上是关于每个程序的行为的。停止定理的证明表明,至少存在一个不正确的程序。这只是非建设性的证明,它不会告诉您哪个程序是不确定的。
MSalters

8
@MSalters我认为隐含的一点是该语句是关于程序的小规模行为,而不是大规模的紧急行为。例如,采用Collat​​z猜想。该算法的各个步骤都很简单且定义明确,但是紧急行为(直到停止之前要进行多少次迭代,以及是否进行了迭代)都无关紧要。-此处非正式地使用“预测”。最好写成“知道任意程序中任何给定的语句将如何执行”。
RM

18

安全不是二进制,而是连续的

非正式地讲,安全是指对错误的反对,其中最常提到的2个是:

  • 内存安全性:该语言及其实现可防止各种与内存相关的错误,例如释放后使用,释放两次,越界访问,...
  • 类型安全:语言及其实现可防止各种类型相关的错误,例如未经检查的强制转换,...

这些不是语言可以阻止的错误的唯一类别,非常需要数据争用自由或死锁自由,正确性的证明很不错,等等。

但是,简单地将不正确的程序很少视为“不安全的”(仅是错误的),并且术语“安全性”通常用于保证影响我们对程序进行推理的能力的保证。因此,具有未定义行为的C,C ++或Go是不安全的。

当然,还有一些语言带有不安全的子集(Java,Rust等),这些子集有目的地划定由开发人员负责维护语言保证且编译器处于“自动”模式的区域。尽管有逃生门,这是一种务实的定义,但语言通常仍被称为安全


7
我会说这是一个格子。
PatJ

1
大多数编程语言实现都有不安全的功能(例如Obj.magic在Ocaml中)。而在实践中,这些都是真正需要的
巴西莱Starynkevitch

4
@BasileStarynkevitch:确实。我认为任何具有FFI的语言都必​​然包含某种程度的不安全,因为调用C函数将需要“固定” GC'ed对象并手动确保双方的签名匹配。
Matthieu M.

15

虽然我不同意DW的回答,但我认为这使“安全”的一部分未得到解决。

如前所述,有多种类型的安全性得到提升。我相信了解为什么会有多个概念是一件好事。每个概念都与这样的想法相关:程序特别遭受某种类型的错误的困扰,并且如果该语言阻止了程序员这样做,那么程序员将无法犯此类特定的错误。

应该注意的是,这些不同的概念因此具有不同的错误类别,并且这些类别不是互斥的,也不是这些类别涵盖了所有形式的错误。仅以DW的2个示例为例,某个内存位置是否持有某个对象的问题既是类型安全又是内存安全的问题。

对“安全语言”的进一步批评来自以下观察结果:禁止某些被认为是危险的结构使程序员有必要提出替代方案。根据经验,好的库可以更好地实现安全性。使用已经过现场测试的代码可以避免产生新的错误。


10
对于本网站而言,它显得有些题外话,因为软件工程并不是一门科学,但是我不同意您的经验陈述。使用优质的库不会为您节省不安全的语言,因为没有保护您避免错误使用它们。安全的语言可让您从库作者那里获得更多保证,并让您获得更多有关正确使用它们的保证。
吉尔斯(Gilles)'所以

3
我与此有关的是MSalters。-“安全的语言使您可以从库作者那里获得更多保证,并让您获得更多的保证,确保您正确使用它们。” 出于所有实际目的,这并非必然。
长颈鹿船长

9

C和Java之间的根本区别在于,如果避免使用Java的某些易于识别的功能(例如,Unsafe名称空间中的功能),则每个人可能尝试的所有动作(包括“错误的”动作)都会产生有限范围的可能结果。尽管这限制了人们在Java中的工作能力-至少在不使用Unsafe命名空间的情况下,它还可以限制由错误程序(或更重要的是-可以正确处理的程序)可能造成的损害有效文件,但并没有特别注意防止错误文件。

传统上,C编译器会在“正常”情况下以标准定义的方式处理许多动作,而“以环境特征”处理许多特殊情况。如果使用的CPU会在发生数值溢出时短路并着火,并且希望避免CPU着火,则需要编写代码来避免数值溢出。但是,如果使用的CPU能够以2的补码形式完美地截断值,那么在这种截断会导致可接受的行为的情况下,不必避免溢出。

现代C向前迈进了一步:即使人们针对的平台自然可以为数值溢出之类的行为定义一个行为,而标准不会对此施加任何要求,但程序的一部分可能会影响程序其他部分的行为。以不受时间和因果关系定律约束的任意方式编程程序。例如,考虑如下内容:

 uint32_t test(uint16_t x)
 {
   if (x < 50000) foo(x);
   return x*x; // Note x will promote to "int" if that type is >16 bits.
 }

给出类似上述内容的“现代” C编译器可能得出结论,因为如果x大于46340,则x * x的计算将溢出,因此它可以无条件执行对“ foo”的调用。请注意,即使在x超出范围的情况下使程序异常终止,或者在这种情况下让函数返回任何值,即使可以接受,使用x超出范围的foo()调用也可能会导致远远超出损坏的范围。这些可能性中的任何一个。传统的C语言不会提供超出编程器和基础平台所提供内容的任何安全保护装置,但会允许安全保护装置限制意外情况造成的损害。现代C会绕过任何安全措施,这些安全措施不能有效地控制一切。


3
@DavidThornley:也许我的例子太微妙了。如果int是32位,x则将被提升为签名int。从原理上看,该标准的作者预期,非怪异的实施将视符号和无符号类型以等价的方式的一些具体情况之外,但GCC有时在打破,如果方法“优化” uint16_t通过uint16_t乘法产生超出INT_MAX结果,即使将结果用作无符号值也是如此。
超级猫

4
好例子。这就是为什么我们应该始终(在GCC或Clang上)进行编译的原因之一-Wconversion
戴维斯洛

2
@Davislor:啊,我只是注意到godbolt颠倒了列出编译器版本的顺序,因此在列表中选择最新版本的gcc会产生最新的而不是最早的版本。我认为警告并不特别有用,因为它很容易标记很多情况,例如return x+1;应该不会出现问题,并且将结果强制转换为uint32_t会扼杀消息而无法解决问题。
超级猫

2
@supercat如果要求编译器将测试放回其他位置,则消除测试毫无意义。
user253751 '18

3
@immibis:“已检查的假设”指令可以允许编译器替换单个测试,该测试可以在循环外进行,而替换多个测试或在循环内执行多次的检查。这比要求程序员添加程序所需的机器代码中不需要的检查要好,以确保编译器不会“优化”满足要求的检查。
超级猫

7

语言中的正确性有多个层次。为了增加抽象度:

  • 很少有程序没有错误(只有那些可以证明其正确性的程序)。其他人已经提到,错误遏制因此是最具体的安全方面。在这方面,在Java和.net等虚拟机中运行的语言通常在这方面更安全:程序错误通常以定义的方式进行拦截和处理。1个
  • 在下一个级别,在编译时而不是在运行时检测到的错误使语言更安全。语法正确的程序在语义上也应该尽可能地正确。当然,编译器无法了解全局,因此这涉及细节级别。强大而富有表现力的数据类型是此级别安全的一方面。有人会说这种语言应该使犯某些类型的错误变得困难(类型错误,超出范围的访问权限,未初始化的变量等)。运行时类型信息(如带有长度信息的数组)可以避免错误。我在大学编程Ada 83,发现一个编译的Ada程序通常比相应的C程序包含的错误少几个数量级。只需使用Ada的用户定义整数类型的能力即可,这些类型在没有显式转换的情况下是无法分配的:整个太空飞船因为高度和高度的混淆而坠毁,而使用Ada可以避免这种情况。

  • 在下一级别,该语言应提供避免样板代码的方法。如果您必须编写自己的容器,它们的排序或它们的串联,或者必须编写自己的容器,那么string::trim()您会犯错误。由于抽象级别的提高,此标准涉及适当的语言以及语言的标准库。

  • 如今,该语言应提供在语言级别上进行并发编程的方法。没有语言支持,并发很难实现,并且可能无法正确完成。

  • 该语言应提供模块化和协作的手段。上面的功能强大,精心设计的用户定义类型有助于创建表达性API。

语言定义在某种程度上是正交的;语言和库文件应有据可查。错误或丢失的文档会导致错误和错误的程序。


1但是,由于通常无法证明虚拟机的正确性,因此这些语言可能有点矛盾,不适合非常严格的安全要求。


1
+1用于清晰的逐层说明。向您提出的问题是,整个太空飞船已经崩溃,因为英尺和米数混乱,而使用Ada可以轻松避免。,您是在谈论由于简单的数学错误而丢失的火星探测器吗?您是否知道他们在太空飞船上使用的语言?
scaaahu

2
@scaaahu是的,我想我指的是这一点。不,我不懂语言。实际上,在阅读报告时,似乎探针发送的数据已由地球上的软件处理,产生了一个数据文件,该数据文件随后用于确定推力水平。简单语言键入不适用于这种情况。顺便说一句,他们在地面软件和数据文件格式方面存在多个问题,这种混乱导致无法及早发现问题。因此,该故事不是强力打字的直接论据,而是一个警示故事。
彼得-恢复莫妮卡

1

请不要说每个PL都有不安全的命令。我们总是可以选择一个安全的子集。

我所知道的每种语言都有编写可以(编译和)运行的非法程序的方法。我所知道的每种语言都有一个安全的子集。那么,您真正的问题是什么?


安全是多维的和主观的。

某些语言有很多“不安全”的操作。其他人的此类操作较少。在某些语言中,默认的做事方式本质上是不安全的。在其他情况下,默认方式是安全的。在某些语言中,有一个明确的“不安全”子集。在其他语言中,根本没有这样的子集。

在某些语言中,“安全性”专门指内存安全性-由标准库和/或运行时提供的服务,在这些运行时中,使访问困难或不可能发生的访问冲突成为可能。换句话说,“安全性”明确包括线程安全性。在其他语言中,“安全性”是指保证程序不会崩溃(此要求包括不允许任何形式的未捕获异常)。最后,在许多语言中,“安全性”指类型安全性-如果类型系统在某些方面是一致的,则称其为“健全的”(顺便说一句,Java和C#没有完全健全的类型系统)。

并且在某些语言中,“安全”的所有不同含义都被视为类型安全的子集(例如Rust和Pony通过类型系统的属性来实现线程安全)。


-1

这个答案更广泛。在最近的几十年中,英语国家社会中某些具有政治倾向的部分已经破坏了安全这个词,因此它们的常用用法几乎没有定义。但是,对于技术主题,我仍然将“安全”和“安全”定义为:防止意外使用某些设备或使意外使用变得更加困难的设备,以及处于这种设备保护之下的状态。
因此,安全的语言具有某种设备来限制特定类别的错误。当然,在某些情况下,限制有时会带来不便甚至不便,但这并不是说“不安全”的语言会导致错误。例如,我的叉子上没有安全软木塞,几十年来,我一直努力地避免进食时刺伤眼睛。当然,比使用软木塞所花费的精力更少。因此,安全性需要付出一定的代价,因此必须对其进行判断。(软木叉子是对史蒂夫·马丁角色的引用)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.