On 12/10/19 12:58 AM, Jason Merrill wrote:
On 11/13/19 2:07 PM, Jeff Chapman wrote:
Attached is a patch that implements pre-c++20 contracts. This comes
from a long running development branch which included ChangeLog entries
as we went, which are included in the patch itself. The repo and
initial wiki are located here:
https://gitlab.com/lock3/gcc-new/wikis/GCC-with-Contracts

Thanks.  I've mostly been referring to the repo rather than the attached patch.  Below are a bunch of comments about the implementation, in no particular order.

We've previously circulated a paper (P1680) which documents our
implementation experience which largely covers new flags and features.
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1680r0.pdf

That paper documents our changes in depth, barring the recently added
-fcontracts flag which is a global opt-in for contracts functionality.
As an overview of what we've done is included below for convenience.

The following switches have been added:

-fcontracts
   enable contract features in general

Flags from the old Working Draft:

-fcontract-build-level=[off|default|audit]
   specify max contract level to generate runtime checks for

-fcontract-continuation-mode=[on|off]
   toggle whether execution resumes after contract failure

Flags from P1290:

-fcontract-assumption-mode=[on|off]
   enable treating axiom level contracts as compile time assumptions
   (default on)

Flags from P1429:

-fcontract-mode=[on|off]
   enable or disable all contract facilities (default on)

-fcontract-semantic=<level>:<semantic>
   specify the concrete semantics for a level

Flags from P1332:

-fcontract-role=<name>:<semantics>
   specify semantics for all levels in a role (default, review, or a
     custom role)
   (ex: opt:assume,assume,assume)

Additional flags:

-fcontract-strict-declarations=[on|off]
   toggle warnings on generalized redecl of member functions
     without contracts (default off)


One assert contract may be present on any block scope empty statement:
   [[ assert contract-mode_opt : conditional-expression ]]

Function declarations have an optional, ordered, list of pre and post
contracts:
   [[ pre contract-mode_opt : conditional-expression ]]
   [[ post contract-mode_opt identifier_opt : conditional-expression ]]


The grammar for the contract-mode_opt portion which configures the
concrete semantics of the contracts is:

contract-mode
   contract-semantic
   contract-level_opt contract-role_opt

contract-semantic
   ignore
   check_never_continue
   check_maybe_continue
   check_always_continue
   assume

contract-level
   default
   audit
   axiom

contract-role
   %default
   %identifier


Contracts are configured via concrete semantics or by an optional
level and role which map to one of the concrete semantics:

   ignore – The contract does not affect the behavior of the program.
   assume – The condition may be used for optimization.
   never_continue – The program terminates if the contract is
                    not satisfied.
   maybe_continue – A user-defined violation handler may terminate the
                    program.
   always_continue – The program continues even if the contract is not
                     satisfied.

I find the proposed always_continue semantics kind of nonsensical, as somewhat evidenced by the contortions the implementation gets into with marking the violation handler as pure.  The trick of assigning the result to a local variable won't work with optimization.

It also depends on the definition of a function that can be overridden to in fact never return.  This seems pretty fatal to it ever getting into the standard.

It's also unclear to me why anyone would want the described semantics. Why would you want a contract check that can be optimized away due to later undefined behavior?  The 0.2 use case from P1332 seems better suited to maybe_continue, because with always_continue such a check will have false negatives, leading to an unpleasant surprise when switching to never_continue.

I'd prefer to treat always_continue as equivalent to maybe_continue. Perhaps with ECF_NOTHROW|ECF_LEAF|ECF_NOVOPS to indicate that it doesn't clobber anything the caller can see, but that's risky if the handler is in fact defined in the same TU with anything that uses contracts.

+  if (strcmp (name, "check_always_continue") == 0
+      || strcmp (name, "always") == 0
+      || strcmp (name, "continue") == 0)

Accordingly, "continue" should mean maybe_continue.

+/* Definitions for C++ contract levels
+   Copyright (C) 1987-2018 Free Software Foundation, Inc.

Should just be 2019 for a new file.

+   Contributed by Michael Tiemann (tiem...@cygnus.com)

This seems inaccurate.  :)

It would also be good to have a reference to P1332 in this header.

+/* Assertion role info.
+
+   FIXME: Why is this prefixed cpp?  */
+struct cpp_contract_role

There seems to be no reason for it, since the struct definition is followed by a typedef; let's remove the prefix.

Any cpp_ prefixes we want to keep should change to cxx to avoid ambiguity with the preprocessor.

+handle_OPT_fcontract_build_level_ (const char *arg)
+{
+  if (contracts_p1332_default || contracts_p1332_review || contracts_p1429)
+    {
+      error ("-fcontract-build-level= cannot be mixed with p1332/p1429");

Hmm, P1429 includes the notion of build level, it's just checked after explicit semantics.  In general, P1429 seems like a compatible extension of the semantics previously in the working paper.

P1332 could also be treated as compatible if we consider the P0542 build level to affect the default role as specified in P1429.  P1680 seems to suggest that this is what you had in mind.

+  // TODO: worry about leaking this?
+  char *name = xstrdup (arg);

Yes, worry. :)
There's no reason to leak it.

+++ b/gcc/c-family/contract.c
@@ -0,0 +1,138 @@
+#include "config.h"
+#include "system.h"

This file needs the introductory copyright block.  It should probably be named cxx-contracts.c.  Can more of the option handling move into this file?

+  return (contract_semantic) CONTRACT_CHECK (t)->base.u.bits.spare1;
+  CONTRACT_CHECK (t)->base.u.bits.spare1 = semantic;

You can't use spare* for data; they are only for allocating bits out of for other named fields.  You could encode the semantic into multiple TREE_LANG_FLAG_* or make it an operand of the expression.

@@ -2730,6 +2809,13 @@ struct GTY(()) lang_decl_fn {
     tree GTY ((tag ("0"))) saved_auto_return_type;
   } GTY ((desc ("%1.pending_inline_p"))) u;

+  /* For the checked version of a guarded function, this points to the var
+     caputring the result of the unchecked function.  */
+  tree unchecked_result;
+  /* For a guarded function with contracts, this is a tree list where
+     the purpose is the location of the contracts and the value is the list of
+     contracts specified in original decl order.  */
+  tree contracts;
 };

Let's not make all functions two words larger to support contracts; this information can go either in an attribute or a separate hash table.  An attribute seems natural for the contracts since that's already the syntax contracts use.  I wouldn't expect you to need a pointer to the unchecked result in the FUNCTION_DECL, that seems like a detail local to the implementation of the checked function.

@@ -6036,6 +6157,8 @@ struct cp_declarator {
      declarator is a pointer or a reference, these attributes apply
      to the pointer, rather than to the type pointed to.  */
   tree std_attributes;
+  /* The contracts, if any. */
+  tree contracts;

Adding to cp_declarator isn't a problem, but again it seems like contracts could be represented as attributes.  In general, it seems like you are working to subvert the attribute mechanisms rather than using them normally, and I'm not sure why.

+  /* Check that assertions are null statements.  */
+  if (attribute_contract_assert_p (contract_attrs))
+    if (token->type != CPP_SEMICOLON)
+      error_at (token->location, "assertions must be followed by %<;%>");

Better I think to handle this further down where [[fallthrough]] has the same requirement.

+++ b/gcc/c-family/c-common.c
@@ -50,6 +50,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "spellcheck.h"
 #include "c-spellcheck.h"
 #include "selftest.h"
+#include "print-tree.h"

I don't see anything that needs this.

+  /* Compare the conditions of the contracts.  */
+  tree old_cond = cp_fully_fold_init (CONTRACT_CONDITION (old_contract)); +  tree new_cond = cp_fully_fold_init (CONTRACT_CONDITION (new_contract));
+  if (!cp_tree_equal (old_cond, new_cond))

Why fold before comparison?  I would think that we want the conditions to be equivalent before folding.

+/* Compare the contract attributes of OLDDECL and NEWDECL. Returns false +   if the contracts match, and true if they differ.  */
+
+static bool
+match_contract_conditions (location_t oldloc, tree old_attrs,
...
+match_contracts (tree olddecl, location_t newloc, tree new_attrs)

These names suggest to me that they would return true if they match, rather than true if there's a problem.  Changing "match" to "mismatched" would be better.

       if (DECL_EXTERNAL (decl) && ! DECL_TEMPLATE_SPECIALIZATION (decl)
          /* Aliases are definitions. */
-         && !alias)
+         && !alias
+         && (DECL_VIRTUAL_P (decl) || !flag_contracts))
        permerror (declarator->id_loc,
                   "declaration of %q#D outside of class is not definition",
                   decl);
+      else if (DECL_EXTERNAL (decl) && ! DECL_TEMPLATE_SPECIALIZATION (decl)
+         /* Aliases are definitions. */
+         && !alias
+         && flag_contract_strict_declarations)
+       warning_at (declarator->id_loc, OPT_fcontract_strict_declarations_, +                   "non-defining declaration of %q#D outside of class", decl);

This change to member function redeclaration seems undesirable; your rationale in P1680 is "for generality", but member functions are already stricter about redeclaration than non-member functions; I don't see a reason to relax this rule just for contracts.  With member functions, there *is* always a canonical first declaration, and people expect the class definition to have its complete interface, of which contracts are a part.

For non-member functions, we still need to complain if contracts are added after we've seen an ODR-use of the function, just like with explicit specializations.

+      /* TODO is there any way to relax this requirement?  */
+      if (DECL_VIRTUAL_P (olddecl) && !TYPE_BEING_DEFINED (DECL_CONTEXT (olddecl)))

Relatedly, I don't think we want to relax this requirement.

+  /* FIXME part of this is taken from store_parm_decls -- is there an existing
+     method that does only this subset of work?  */

I think you're looking for inject_parm_decls; that's what deferred noexcept parsing uses.

+  /* If we're entering a class member; ensure current_class_ptr and
+     current_class_ref point to the correct place.  */

And this is inject_this_parameter.

+/* Build and return a new string representing the unchecked function name
+   corresponding to the name in IDENT. */
+
+// FIXME: are we sure we shouldn't just mangle or use some existing machinery?
+static const char *
+get_contracts_internal_decl_name (const char *ident)

You absolutely should use the mangler, and should propose a mangling to the ABI committee at https://github.com/itanium-cxx-abi/cxx-abi/issues

For comparison, transactional memory extra entry points are encoded by inserting "GTt" after the _Z in the mangling of a function; something similar seems appropriate here.

+/* Like copy_fn; rebuild UNCHECKED's DECL_SAVED_TREE to have its own locals +   and point to its own DECL_ARGUMENTS and DECL_RESULT instead of pointing at
+   CHECKED's.
+
+   Unlike copy_fn, UNCHECKED (the target) does not need to be
+   current_function_decl.  */

Why not leave the function the user declared as the unchecked function (just changing some linkage properties) and create a new function for the checked wrapper?

+         && (declarator->kind != cdk_function
+             || !inner_declarator || inner_declarator->kind != cdk_id))

I think you want function_declarator_p.

+  // TODO: can this contextually be ECF_NOTHROW if inside a noexcept?

That doesn't sound worthwhile to me.

+/* Parse a conditional-expression.  */
+/* FIXME: should callers use cp_parser_constant_expression?  */

It doesn't really matter since C++11 generalized constant expressions; we don't need to enforce their rules in the parser anymore.

+/* Return the source text between two locations.  */
+
+static char *
+get_source (location_t start, location_t end)

This seems like it belongs in libcpp.  It also needs to

+  /* FIXME how should we handle newlines and runs of spaces?  */
+  char buf[1024 + 4]{};
+  char *res = buf;
+  size_t len = 1024;

Obstacks work very well for temporary buffers like this.

+/* Pretty print an expression and return the result as a char*.  */
+
+static char*
+stringify_tree (tree expr)

Is there a reason not to use expr_to_string?

+  /* TODO: If this is a postcondition and the return type is deduced,
+     then we need to cache tokens and parse when the function is
+     defined.

Yes.

+     Would it be okay to declare the result decl as having a deduced
+     return type also?

For a template, yes.  For a non-templated function, no.

+     NOTE: You can't declare a deduced return type function with
+     postconditions and not define it.  */

Well, you can, but you can't call it, so it doesn't matter if we haven't figured out the contracts.

+   FIXME: There are probably earlier exits than end of file. What
+   about a semicolon?  */

You could use cp_parser_skip_to_closing_parenthesis_1 to look for an un-nested ] and then complain if it isn't followed by another ].

+/* Build and return an argument list using all the arguments passed to the +   (presumably guarded) FUNCTION_DECL FN.  This can be used to forward all of +   FN's arguments to a function taking the same list of arguments -- namely
+   the unchecked form of FN. */

You will want to use forward_parm in this function.

+      if (VOID_TYPE_P (t))
+       continue;

There are no void parameters in DECL_ARGUMENTS (only in TYPE_ARG_TYPES).

+      error ("guarded function %q+D overriding non-guarded function",

The term "guarded" doesn't seem to appear in any of the contracts papers other than yours, so let's word this differently.

+      error ("guarded function %q+D overriding non-guarded function",
+            overrider);
+      inform (DECL_SOURCE_LOCATION (basefn),
+             "overridden function is %qD", basefn);
+      return 0; // FIXME?

What's to fix?

+  /* FIXME: Why do we have two unrelated statement lists?  */
+  tree def = push_stmt_list();
+  tree body = begin_compound_stmt (BCS_NORMAL);

You probably want begin_function_body instead of push_stmt_list.

+  finish_function_body (TREE_VALUE (def));

And then a finish_compound_stmt before the finish_function_body.

+  /* FIXME we're marking the unchecked fn decl as not virtual, so why do we
+     need to disallow virtual when building the call? */

Indeed, the disallow_virtual argument only matters for virtual functions, so either value is fine.

+  /* FIXME it may make more sense to have the checked function be concrete +     with the unchecked be virtual since all checked function overrides must
+     be equivalent due to the contract matching requirement.  */

Doing it that way would allow better inlining of the checking wrapper, but would require some way to specify virtual or non-virtual calling from the checking wrapper to the unchecked function.

+  /* FIXME temporarily inlined from finish_compound_stmt except for actually +     adding the compound statement to the current statement list. We need +     several parts of this logic to handle normal and template parsing for +     postconditions, but is there something better we can use instead?  */

You could push/pop_stmt_list around the call to finish_compound_stmt to capture the result.

+  tree level_str = build_string_literal (strlen (level) + 1, level);
+  tree role_str = build_string_literal (strlen (role) + 1, role);

Maybe combine these into a single string argument?

+  /* We never want to accidentally instantiate templates.  */
+  if (code == TEMPLATE_DECL)
+    return *tp; /* FIXME? */

This seems unlikely to have the desired effect; we should see template instantiations as FUNCTION_DECL or VAR_DECL.  I'm also not sure what the cp_tree_defined_p check is trying to do; surely using defined functions and variables can also lead to runtime code?

+  /* FIXME: This code gen will be moved to gimple.  */

It should be simple enough to move to cp_genericize_r.

+  /* The condition is converted to bool.
+
+     FIXME: When we instantiate this, the input location is in entirely
+     the wrong place.  */

It should work to attach the location to the contract _STMT.

@@ -14156,6 +14156,7 @@ gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p,
              goto dont_recalculate;

            default:
+              debug_tree (*expr_p);

Leftover debugging code.

+      /* If we don't have contracts and haven't already set the deferred
+        contracts to a "no initial contracts" sentinel, do so now to support
+        flag_contract_strict_declarations.  */

What is the advantage of this sentinel over NULL_TREE?

+// TODO FIXME override stops parsing of attrs silently?

Any more information about this?

+    /* FIXME: should the contracts on a friend decl be a complete class
+       context for the type being currently defined? */
+  /* FIXME contracts can be parsed inside of a class when the decl is a friend? */

I would expect all contract attributes in a class to be complete class contexts, whether the function is a member or friend.

+  /* FIXME Is this right for friends? Can a friend refer to a static member?
+     Can a friend's contracts refer to our privates? Turning them into
+     [[assert]]s inside the body of the friend itself certainly lets them do
+     so. */

P0542 says contracts are limited to the accessibility of the function itself, so a friend cannot refer to private members.

+  /* FIXME Do instantiations mean anything in the context of contracts?  */

No; I'm not sure what they mean for default arguments.

Now I understand again what they mean for default arguments: if we need to instantiate a member function before its default arguments have been parsed, we stick the same DEFERRED_PARSE in the default arguments of the instantiation, and then replace any of those with the parsed result once we have it. Since you wait to substitute contracts until you are instantiating the function definition, you shouldn't need anything like that.

+      DECL_DEFERRED_CONTRACTS (decl) = chainon (
+         DECL_DEFERRED_CONTRACTS (decl),
+         build_tree_list (original_loc, fn->contracts));

This indentation pattern occurs pretty often in the patch, but doesn't match the usual style in GCC sources; there's only one occurrence of a left paren at the end of a line.  Please move it to the beginning of the argument list on the next line.  The following lines also seem more indented than necessary.

Jason

Reply via email to