Hi Martin and Michael, On 11/29/22 17:58, Uecker, Martin wrote:
Hi, Am Dienstag, dem 29.11.2022 um 15:44 +0000 schrieb Michael Matz:Hey, On Tue, 29 Nov 2022, Uecker, Martin wrote:It does not require any changes on how arrays are represented. As part of VM-types the size becomes part of the type and this can be used for static or dynamic analysis, e.g. you can - today - get a run-time bounds violation with the sanitizer: void foo(int n, char (*buf)[n]) { (*buf)[n] = 1; }This can already statically analyzed as being wrong, no need for dynamic checking.In this toy example, but in general in can be checked only at run-time by using the information about the dynamic bound.What I mean is the checking of the claimed contract. Above you assure for the function body that buf has n elements.Yes.This is also a pre-condition for calling this function and _that_ can't be checked in all cases because: void foo (int n, char (*buf)[n]) { (*buf)[n-1] = 1; } void callfoo(char * buf) { foo(10, buf); } buf doesn't have a known size.This does not type check.And a pre-condition that can't be checked is no pre-condition at all, as only then it can become a guarantee for the body.The example above should look like: void foo(int n, char (*buf)[n]); void callfoo(char (*buf)[12]) { foo(10, buf); } This could be checked by an UB sanitizer as calling the function with an argument of incompatible type is UB (but we currently do not do this) If you think about void foo(int n, char buf[n]); void callfoo(char *buf) { foo(10, buf); } Then you are right that this can not be checked at this time. But this does not mean it is useless because we still can detect inconsistencies in other cases: void callfoo(int n, char buf[n - 1]) { foo(n, buf); } We could also - in the future - have a warning about all situations where bound information is lost, making sure that preconditions are always checked for people who consistently use these annotations.The compiler has no choice than to trust the user that the pre- condition for calling foo is fulfilled. I can see how being able to just check half of the contract might be useful, but if it doesn't give full checking then any proposal for syntax should be even more obviously orthogonal than the current one.Your argument is not clear to me.For void foo(int n, char buf[n]); it semantically has no meaning according to the C standard, but a compiler could still warn.Hmm? Warn about what in this decl?I meant, we could warn about something like this because it is likely an error: void foo(int n, char buf[n]) { buf[n] = 1; }It could also warn for void foo(int n, char buf[n]); int main() { char buf[9]; foo(buf); }You mean if you write 'foo(10,buf)' (the above, as is, is simply a syntax error for non-matching number of args). Or was it a mispaste and you mean the one from the godbolt link, i.e.:I meant: char buf[9]; foo(10, buf); In fact, it turns out we warn already: https://godbolt.org/z/qcvsv87Evvoid foo(char buf[10]){ buf[9] = 1; } int main() { char buf[9]; foo(buf); } ? If so, yeah, we warn already. I don't think this is an argument for (or against) introducing new syntax. ...It is argument for having this syntax, because we could extend such warning (those we already have and those we could still add) to more common cases such as void foo(char buf[.n], size_t n); In my opinion, this would a huge step forward for safety of C programs as we already have a lot of infrastructure for checking bounds. Of course, the existing GNU extension would achieve the same thing: void foo(size_t n; char buf[n], size_t n);But in general: This feature is useful not only for documentation but also for analysis.Which feature we're talking about now? The ones you used all work today, as you demonstrated. I thought we would be talking about that ".whatever" syntax to refer to arbitrary parameters, even following ones? I think a disrupting syntax change like that should have a higher bar than "in some cases, depending on circumstance, we might even be able to warn".We can use our existing features and then apply them to cases where the bound is specified after the pointer, which is more common in practice.
Yep; basically adding some (not perfect, but some) static analysis to many libc function calls.
Also, considering the issues with sizeof and arrays, and the lack of a _Nitems() [proposed as _Lengthof()] operator, there's a lot of manual work in array (read pointer) parameters.
However, a hypothetical _Nitems() operator could make use of this syntactic sugar, and be more useful than just providing static analysis. Using _Nitems() on a VMT (including pointer parameters) could be specified to return the number of elements, so I foresee code like:
void foo(int arr[nmemb], size_t nmemb) { // _Nitems() evaluates to nmemb for (size_t i = 0; i < _Nitems(arr); i++) arr[i] = i; } void bar(int arr[]) { // Constraint violation for (size_t i = 0; i < _Nitems(arr); i++) arr[i] = i; }This is probably the most useful part of this feature (but admittedly it's not only about this feature, or even could be added without this feature).
Martin
Cheers, Alex -- <http://www.alejandro-colomar.es/>
Attachment:
OpenPGP_signature
Description: OpenPGP digital signature