处理表达式时,如何检查Ada中的潜在溢出?

时间:2018-09-03 15:17:37

标签: ada

我对Ada相对较新,并且一直在使用Ada2005。但是,我觉得这个问题与所有语言都有关。

我目前正在使用诸如Codepeer之类的静态分析工具来解决我的代码中的潜在漏洞。

我要讨论的一个问题是在分配可能导致变量溢出的表达式之前如何处理检查。

这可以用一个例子更好地解释。假设我有一个无符号32位整数类型的变量。我正在为此变量CheckMeForOverflow分配一个表达式:

CheckMeForOverflow := (Val1 + Val2) * Val3;

我的难题是在这种情况下如何有效地检查溢出-这似乎在代码中经常出现。是的,我可以这样做:

if ((Val1 + Val2) * Val3) < Unsigned_Int'Size then
    CheckMeForOverflow := (Val1 + Val2) * Val3;
end if;

我的问题是,检查表达式似乎效率低下,如果没有溢出的可能性,则立即分配相同的表达式。

但是,当我在网上看时,这似乎很普遍。谁能解释更好的选择或解释为什么这是一个好选择?我不希望这些分散在我的代码中。

我还意识到我可以制作另一个更大类型的变量来保存表达式,对新变量进行求值,然后将该变量的值分配给CheckMeForOverflow,但这又意味着制作一个新变量并使用它只是执行一次检查,然后不再使用它。这似乎很浪费。

有人可以提供一些见解吗?

非常感谢!

2 个答案:

答案 0 :(得分:3)

我个人会做这样的事情

begin
   CheckMeForOverflow := (Val1 + Val2) * Val3;
exception
   when constraint_error =>
                 null; --  or log that it overflowed
end;

但是请注意,您的变量不能有可用的值。

比if构造更清晰,我们不会执行两次计算。

答案 1 :(得分:3)

这正是SPARK可以解决的问题。假定您对计算的输入有特定的假设,它可以证明您不会出现运行时错误。

如果您在此软件包中使用了像No_Overflow这样的简单函数,那么

with Interfaces; use Interfaces;

package Show_Runtime_Errors is

   type Unsigned_Int is range 0 .. 2**32 - 1;
   function No_Overflow (Val1, Val2, Val3 : Unsigned_Int) return Unsigned_Int;

end Show_Runtime_Errors;


package body Show_Runtime_Errors is

   function No_Overflow (Val1, Val2, Val3 : Unsigned_Int) return Unsigned_Int is
      Result : constant Unsigned_Int := (Val1 + Val2) * Val3;
   begin
      return Result;
   end No_Overflow;

end Show_Runtime_Errors;

然后在其上运行SPARK时,会得到以下信息:

Proving...
Phase 1 of 2: generation of Global contracts ...
Phase 2 of 2: flow analysis and proof ...
show_runtime_errors.adb:4:55: medium: range check might fail (e.g. when Result = 10)
show_runtime_errors.adb:4:55: medium: overflow check might fail (e.g. when
   Result = 9223372039002259450 and Val1 = 4 and Val2 = 2147483646 and
   Val3 = 4294967293)
gnatprove: unproved check messages considered as errors
exit status: 1

现在,如果您像这样向No_Overflow添加一个简单的前提条件:

function No_Overflow (Val1, Val2, Val3 : Unsigned_Int) return Unsigned_Int with
   Pre => Val1 < 2**15 and Val2 < 2**15 and Val3 < 2**16;

然后SPARK产生以下内容:

Proving...
Phase 1 of 2: generation of Global contracts ...
Phase 2 of 2: flow analysis and proof ...
Success!

您在输入范围上的实际前提条件显然取决于您的应用程序。

替代方法是您假定的解决方案,其中在对表达式求值之前在代码中放置了许多显式的防护,或者通过异常处理捕获运行时错误。与这些方法相比,SPARK的优势在于,如果可以提前证明不会出现运行时错误,则无需通过运行时检查来构建软件。

请注意,前提条件是Ada 2012的功能。您还可以在整个代码中使用pragma Assert,SPARK可以利用这些if ((Val1 + Val2) * Val3) < Unsigned_Int'Size then CheckMeForOverflow := (Val1 + Val2) * Val3; end if; 来进行证明。

有关SPARK的更多信息,请参见此处的教程: https://learn.adacore.com/courses/intro-to-spark/index.html

要自己尝试,可以在上面的示例中粘贴上面的代码: https://learn.adacore.com/courses/intro-to-spark/book/03_Proof_Of_Program_Integrity.html#runtime-errors

顺便提一下,您建议的代码:

Unsigned_Int'Size

由于以下两个原因而无法工作:

  1. Unsigned_Int是表示Unsigned_Int'Last所需的位数。您可能希望改用((Val1 + Val2) * Val3)
  2. Unsigned_Int'Last可能会在与 Try Dim lnard As New MySqlConnection("server = localhost; userid = root; password = 12345; DATABASE = sms;") Dim da As MySqlDataAdapter = Nothing Dim DT As New DataTable Dim sql As String = " " sql = "select distinct contactNumber from students" lnard .Open() da = New MySqlDataAdapter(sql, lnard) da.Fill(DT) With combobox1 .DataSource = DT .DisplayMember = "contactNumber " .ValueMember = "contactNumber " End With lnard .Close() Catch ex As Exception End Try 进行比较之前溢出。因此,您将在此时生成一个异常,并使其崩溃或在异常处理程序中对其进行处理。