acsl-language / acsl

Sources for the ANSI/ISO C Specification Language manual
Other
49 stars 8 forks source link

Pragma-based syntax #21

Open nemequ opened 7 years ago

nemequ commented 7 years ago

Adpated from a post on the frama-c mailing list:

Using the first example from the tutorial:

/*@ ensures \result >= x && \result >= y;
    ensures \result == x || \result == y;
*/
int max (int x, int y) {
  return (x > y) ? x : y;
}

A pragma-based version might look something like:

#pragma acsl ensures \result >= x && \result >= y;
 #pragma acsl ensures \result == x || \result == y;
int max (int x, int y) {
  return (x > y) ? x : y;
}

One major advantage of the pragma syntax is integration with the preprocessor. For example, let's say I use a macro to define several variants of a function:

#define DEFINE_MAX_FN(T, id) \
  T id##_max (T x, T y) { \
    return (x > y) ? x : y; \
  }
DEFINE_MAX_FN(int, int)
DEFINE_MAX_FN(unsigned int, uint)
DEFINE_MAX_FN(int64_t, int64)

I realize that (ab)using the preprocessor like this is often considered to be something of an anti-pattern, but there are times where the benefits from code reuse vastly outweigh the decreased readability. I know several of my own projects would have been impractical without this feature.

As far as I know, with ACSL there is no good way to annotate the different versions; I would need to add a comment for each one:

/*@ ensures \result >= x && \result >= y;
    ensures \result == x || \result == y;
*/
DEFINE_MAX_FN(int, int)
/*@ ensures \result >= x && \result >= y;
    ensures \result == x || \result == y;
*/
DEFINE_MAX_FN(unsigned int, uint)
/*@ ensures \result >= x && \result >= y;
    ensures \result == x || \result == y;
*/
DEFINE_MAX_FN(int64_t, int64)

Which is especially onerous if the macro is considered to be part of the user-facing API and not just an implementation detail. This is pretty common for things like test harnesses and data structure implementations.

With a pragma-based implementation the annotations could be moved into the macro:

#define DEFINE_MAX_FN(T, id) \
  _Pragma("acsl ensures \\result >= x && \\result >= y") \
  _Pragma("acsl ensures \\result == x || \\result == y") \
  T id##_max (T x, T y) { \
    return (x > y) ? x : y;
  }

Just to give a bit of context, I'm interested in adding (partial) ACSL support to C compilers; not for full formal verification, but to help the compilers provide better compile-time warnings and errors.

I think the pragma-based syntax would also be more attractive for compilers as it would likely be easier to hook in to existing mechanisms in place to handle things like OpenMP, Cilk++, and compiler-specific directives. It's also seems a bit more palatable since the pragma mechanism is part of the C99 and C11 standards.

@vprevosto responded with:

I see your point. This might indeed be worth considering, even though I think that some clarifications are needed on what exactly can go after an acsl pragma. In particular, you seem to want to be able to split a single annotation into several pragmas, which implies an obvious question: how do we merge them? In particular, we should take care of easily distinguishing global annotations from function contracts, as in:

_Pragma("acsl lemma foo: \\true;")
_Pragma("acsl requires \\true;")
_Pragma("acsl ensures \\true;")
int f() { return 0; }

That's definitely a problem. I think that allowing people to split annotations across multiple pragmas is important for readability, though.

IMHO perhaps the most natural thing would be to have keywords like lemma apply to a single statement, but also add a begin/end syntax for multi-statement annotations. So, the above example would translate into something like

/*@ lemma foo: \true; */
/*@ requires: \true;
    ensures: \true;
  */
int f() { return 0; }

However, you changed the code to something like

#pragma acsl begin lemma foo: \true;
#pragma acsl requires: \true;
#pragma acsl ensures: \true;
#pragma acsl end lemma;

You would end up with

/*@ lemma foo: \true
    requires: \true;
    ensures: \true;
 */
int f() { return 0; }

Of course you could also go with end lemma foo, end foo, or even just end.