C# 代码契约:什么可以静态证明,什么不能?

发布于 2024-10-17 23:22:06 字数 569 浏览 1 评论 0原文

我可能会说我对代码合同非常熟悉:我已经阅读并理解了大部分 用户手册并且已经使用它们很长一段时间了,但我仍然有疑问。当我搜索“未经验证的代码合约”时,有相当多的点击,所有这些都询问为什么他们的特定声明无法被静态证明。尽管我可以做同样的事情并发布我的具体场景(顺便说一句:

在此处输入图像描述),

我更愿意理解为什么任何代码合同条件可以或无法证明。有时我对它所证明的东西印象深刻,有时我……好吧……礼貌地说:绝对不印象深刻。如果我想理解这一点,我想知道静态检查器使用的机制。我确信我会通过经验学习,但我在各处喷洒 Contract.Assume 语句以使警告消失,而且我觉得这不是代码契约的用途。谷歌搜索对我没有帮助,所以我想问问你们的经验:你们看到了什么(不明显的)模式?是什么让你看到了光明?

I might say I'm getting quite familiar with Code Contracts: I've read and understood most of the user manual and have been using them for quite a while now, but I still have questions. When I search SO for 'code contracts unproven' there are quite a few hits, all asking why their specific statement couldn't be statically proven. Although I could do the same and post my specific scenario (which is btw:

enter image description here),

I'd much rather understand why any Code Contract condition can or can't be proven. Sometimes I'm impressed with what it can prove, and sometimes I'm... well... to say it politely: definately not impressed. If I want to understand this, I'd like to know the mechanisms the static checker uses. I'm sure I'll learn by experience, but I'm spraying Contract.Assume statements all over the place to make the warnings go away, and I feel like that's not what Code Contracts are meant for. Googling didn't help me, so I want to ask you guys for your experiences: what (unobvious) patterns have you seen? And what made you see the light?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

似最初 2024-10-24 23:22:06

你们的施工合同没有得到满足。由于您正在引用对象的字段 (this.data),因此其他线程可能有权访问该字段,并且可能会在假设和第一个参数解析和第三个参数解析之间更改其值。 (ei,它们可能是三个完全不同的数组。)

您应该将数组分配给局部变量,然后在整个方法中使用该变量。然后分析器将知道约束得到满足,因为没有其他线程能够更改引用。

var localData = this.data;
if (localData == null) return;
byte[] newData = new byte[localData.Length]; // Or whatever the datatype is.
Array.Copy(localData, newData, localData.Length); // Now, this cannot fail.

这具有额外的好处,不仅满足约束,而且实际上使代码在许多情况下更加健壮。

我希望这能让您找到问题的答案。我实际上无法直接回答您的问题,因为我无法访问包含静态检查器的 Visual Studio 版本。 (我使用的是 VS2008 Pro。)我的答案是基于我自己对代码的目视检查得出的结论,并且静态合同检查器似乎使用了类似的技术。我很着迷!我需要给自己找一个。 :-D

更新: (有很多猜测)

经过反思,我认为我可以很好地猜测什么可以被证明或不能被证明(即使无法访问静态检查器)。正如另一个答案中所述,静态检查器不进行过程间分析。因此,随着多线程变量访问的迫在眉睫(如在OP中),静态检查器只能有效地处理局部变量(如下定义)。

我所说的“局部变量”是指不能被任何其他线程访问的变量。这将包括在方法中声明或作为参数传递的任何变量,除非参数用 refout 修饰,或者变量是在匿名方法中捕获的。

如果局部变量是值类型,那么它的字段也是局部变量(递归地依此类推)。

如果局部变量是引用类型,则只有引用本身(而不是其字段)可以被视为局部变量。即使对于在方法内构造的对象也是如此,因为构造函数本身可能会泄漏对构造对象的引用(例如,用于缓存的静态集合)。

只要静态检查器不进行任何过程间分析,对上述定义的非局部变量所做的任何假设都可以随时失效,因此在静态分析中会被忽略。

例外 1:由于运行时已知字符串和数组是不可变的,因此只要字符串或数组变量本身是本地的,它们的属性(例如长度)就可以进行分析。这不包括可由其他线程可变的数组内容。

例外 2:运行时可能知道数组构造函数不会泄漏对构造数组的任何引用。因此,在方法主体内构造且未泄漏到方法外部(作为参数传递给另一个方法、分配给非局部变量等)的数组具有也可以被视为局部变量的元素。

这些限制看起来相当繁重,我可以想象有几种方法可以改进,但我不知道已经做了什么。理论上,静态检查器可以完成以下一些其他事情。手头有它的人应该检查一下已经做了什么和没有做什么:

  • 它可以确定构造函数是否不会泄漏对对象或其字段的任何引用,并将如此构造的任何对象的字段视为局部变量。
  • 可以对其他方法进行无泄漏分析,以确定在该方法调用之后传递给方法的引用类型是否仍然可以被认为是本地的。
  • 用 ThreadStatic 或 ThreadLocal 修饰的变量可以被视为局部变量。
  • 可以给出选项来忽略使用反射修改值的可能性。这将允许引用类型上的私有只读字段或静态私有只读字段被视为不可变。此外,当启用此选项时,私有或内部变量 X lock(X){ /**/ } 构造中被访问过,并且不是leaked 可以被认为是局部变量。然而,这些东西实际上会降低静态检查器的可靠性,所以这有点不确定。

另一种可能开启大量新分析的可能性是以声明方式将变量和使用它们的方法(等等递归地)分配给特定的唯一线程。这将是对该语言的重大补充,但可能是值得的。

The contract in your construction is not satisfied. Since you are referencing an object’s field (this.data), other threads may have access to the field and may change its value between the Assume and the first parameter resolution and the third parameter resolution. (e.i., they could be three completely different arrays.)

You should assign the array to a local variable, then use that variable throughout the method. Then the analyzer will know that the constraints are being satisfied, because no other threads will have the ability to change the reference.

var localData = this.data;
if (localData == null) return;
byte[] newData = new byte[localData.Length]; // Or whatever the datatype is.
Array.Copy(localData, newData, localData.Length); // Now, this cannot fail.

This has the added benifit of not only satisfying the constraint, but, in reality, making the code more robust in many cases.

I hope this leads you to the answer to your question. I could not actually answer your question directly, because I do not have access to a version of Visual Studio that includes the static checker. (I'm on VS2008 Pro.) My answer is based on what my own visual inspection of the code would conclude, and it appears that the static contract checker uses similar techniques. I am intreagued! I need to get me one of them. :-D

UPDATE: (Lots of speculation to follow)

Upon reflection, I think I can do a pretty good guess of what can or can't be proven (even without access to the static checker). As stated in the other answer, the static checker does not do interprocedural analysis. Therefore, with the looming possibility of multi-threaded variable accesses (as in the OP), the static checker can only deal effectively with local variables (as defined below).

By "local variables" I mean a variable that cannot be accessed by any other thread. This would include any variables declared in the method or passed as a parameter, unless the parameter is decorated with ref or out or the variable is captured in an anonymous method.

If a local variable is a value-type, then its fields are also local variables (and so on recursively).

If a local variable is a reference-type, then only the reference itself—not its fields—can be considered a local variable. This is true even of an object constructed within the method, since a constructor itself may leak a reference to the constructed object (say to a static collection for caching, for example).

So long as the static checker does not do any interprocedural analysis, any assumptions made about variables that are not local as defined above can be invalidated at any time, and, therefore, are ignored in the static analysis.

Exception 1: since strings and arrays are known by the runtime to be immutable, their properties (such as Length) are subject to analysis, so long as the string or array variable itself is local. This does not include the contents of an array which are mutable by other threads.

Exception 2: The array constructor may be known by the runtime not to leak any references to the constructed array. Therefore, an array that is constructed within the method body and not leaked outside of the method (passed as a parameter to another method, assigned to a non-local variable, etc.) has elements that may also be considered local variables.

These restrictions seem rather onerous, and I can imagine several ways this could be improved, but I don't know what has been done. Here are some other things that could, in theory, be done with the static checker. Someone who has it handy should check to see what has been done and what hasn't:

  • It could determine if a constructor does not leak any references to the object or its fields and consider the fields of any object so constructed to be local variables.
  • A no-leaks analysis could be done on other methods to determine whether a reference type passed to a method can still be considered local after that method invocation.
  • Variables decorated with ThreadStatic or ThreadLocal may be considered local variables.
  • Options could be given to ignore the possibility of using reflection to modify values. This would allow private readonly fields on reference types or static private readonly fields to be considered immutable. Also, when this option is enabled, a private or internal variable X that is only ever accessed inside a lock(X){ /**/ } construction and which is not leaked could be considered a local variable. However, these things would, in effect, reduce the reliability of the static checker, so that's kinda iffy.

Another possibility that could open up a lot of new analysis would be declaratively assigning variables and the methods that use them (and so on recursively) to a particular unique thread. This would be a major addition to the language, but it might be worth it.

烟沫凡尘 2024-10-24 23:22:06

简而言之,静态代码分析器似乎非常有限。例如,它不会检测

readonly string name = "I'm never null";

为不变量。从我在 MSDN 论坛上收集到的信息来看,它会自行分析每种方法(出于性能原因,而不是人们认为它会变得更慢),这限制了它在验证代码时的知识。

为了在证明正确性的学术崇高目标和能够完成工作之间取得平衡,我诉诸于装饰单个方法(甚至根据需要甚至是类),

[ContractVerification(false)]

而不是在逻辑中撒上大量假设。这可能不是使用 CC 的最佳实践,但它确实提供了一种无需取消选中任何静态检查器选项即可消除警告的方法。为了不丢失此类方法的前置/后置条件检查,我通常添加具有所需条件的存根,然后调用排除的方法来执行实际工作。

我自己对代码契约的评估是,如果您仅使用官方框架库并且没有大量遗留代码(例如,在开始新项目时),那么它非常好。任何其他的事情都是快乐和痛苦的混合体。

The short answer is that the static code analyzer appears to be very limited. For instance, it does not detect

readonly string name = "I'm never null";

as being an invariant. From what I can gather on MSDN forums, it analyzes every method by itself (for performance reasons, not that one should think it could get much slower), which limits its knowledge when verifying the code.

To strike a balance between the academically lofty goal of proving correctness and being able to get work done, I've resorted to decorating individual methods (or even classes, as needed) with

[ContractVerification(false)]

rather than sprinkle the logic with lots of Assumes. This may not be best practice for using CC, but it does provide a way to get rid of warnings without unchecking any of the static checker options. In order not to lose pre/post-condition checks for such methods I generally add a stub with the desired conditions and then invoke the excluded method to perform the actual work.

My own assessment of Code Contracts is that it's great if you're only using the official framework libraries and do not have a lot of legacy code (e.g. when starting a new project). Anything else and it's a mixed bag of pleasure and pain.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文