On 12/1/25 10:18 PM, Iain Sandoe wrote:
From: Nina Ranns <[email protected]>

Changes since v1
  - fixed a merge error in the removal of C++2a code.
  - rebased onto r16-5785-g3b30d09ac7bbf8 (includes change to default to
    C++20).

--- 8< ---

This a (currently GCC-only) extension that implements caller-side
checking of pre and post conditions.  It is completely in scope
with the C++26 CDIS wording, but is not mandated.

The implementation here allows applying caller or callee-side
checking independently.

gcc/c-family/ChangeLog:

        * c.opt (fcontracts-definition-check=,
        fcontracts-client-check=): New.

gcc/cp/ChangeLog:

        * call.cc (build_cxx_call): Where enabled, wrap calls to
        functions with contract specifiers.
        * contracts.cc (enum contract_match_kind): Move to contracts
        header.
        (build_contract_condition_function): Copy caller-side wrapper
        state.
        (set_contract_wrapper_function, get_contract_wrapper_function,
        get_orig_func_for_wrapper, contracts_fixup_cdtorname,
        build_contract_wrapper_function,
        get_or_create_contract_wrapper_function): New.
        (start_function_contracts): Handle caller-side wrappers.
        (maybe_apply_function_contracts): Likewise.
        (copy_and_remap_contracts): Likewise.
        (should_contract_wrap_call, maybe_contract_wrap_call,
        define_contract_wrapper_func, emit_contract_wrapper_func): New.
        (finish_function_contracts): Handle caller-side wrappers.
        (get_src_loc_impl_ptr): Likewise.
        * contracts.h (DECL_IS_WRAPPER_FN_P): New.
        (enum contract_match_kind): Moved from contracts.cc.
        (copy_and_remap_contracts): Allow selection on the specific
        contract kind.
        (maybe_contract_wrap_call, emit_contract_wrapper_func): New.
        (set_decl_contracts): Delete dead code.
        * cp-tree.h (struct lang_decl_fn): Add wrapper function bit.
        (DECL_CONTRACT_WRAPPER): New.
        * decl2.cc (c_parse_final_cleanups): Emit wrappers.
        * mangle.cc (write_mangled_name): Add mangling for wrappers.

gcc/testsuite/ChangeLog:

        * g++.dg/contracts/cpp26/callerside-checks/callerside-checks-all.C: New 
test.
        * 
g++.dg/contracts/cpp26/callerside-checks/callerside-checks-non-trivial.C: New 
test.
        * g++.dg/contracts/cpp26/callerside-checks/callerside-checks-none.C: 
New test.
        * g++.dg/contracts/cpp26/callerside-checks/callerside-checks-pre.C: New 
test.
        * g++.dg/contracts/cpp26/callerside-checks/ctor.C: New test.
        * g++.dg/contracts/cpp26/callerside-checks/freefunc-noexcept-post.C: 
New test.
        * g++.dg/contracts/cpp26/callerside-checks/freefunc-noexcept-pre.C: New 
test.
        * 
g++.dg/contracts/cpp26/definition-checks/contract-assert-no-def-check.C: New 
test.
        * g++.dg/contracts/cpp26/non-trivial-ice.C: New test.

Co-Authored-by: Iain Sandoe <[email protected]>
Co-Authored-by: Ville Voutilainen <[email protected]>
Signed-off-by: Iain Sandoe <[email protected]>
---
  gcc/c-family/c.opt                            |  29 ++
  gcc/cp/call.cc                                |   5 +-
  gcc/cp/contracts.cc                           | 327 +++++++++++++++++-
  gcc/cp/contracts.h                            |  21 +-
  gcc/cp/cp-tree.h                              |   8 +-
  gcc/cp/decl2.cc                               |  11 +-
  gcc/cp/mangle.cc                              |   4 +
  .../callerside-checks/callerside-checks-all.C |  52 +++
  .../callerside-checks-non-trivial.C           |  18 +
  .../callerside-checks-none.C                  |  64 ++++
  .../callerside-checks/callerside-checks-pre.C |  65 ++++
  .../contracts/cpp26/callerside-checks/ctor.C  |  23 ++
  .../freefunc-noexcept-post.C                  |  49 +++
  .../callerside-checks/freefunc-noexcept-pre.C |  49 +++
  .../contract-assert-no-def-check.C            |  25 ++
  .../g++.dg/contracts/cpp26/non-trivial-ice.C  |  21 ++
  16 files changed, 753 insertions(+), 18 deletions(-)
  create mode 100644 
gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/callerside-checks-all.C
  create mode 100644 
gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/callerside-checks-non-trivial.C
  create mode 100644 
gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/callerside-checks-none.C
  create mode 100644 
gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/callerside-checks-pre.C
  create mode 100644 
gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/ctor.C
  create mode 100644 
gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/freefunc-noexcept-post.C
  create mode 100644 
gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/freefunc-noexcept-pre.C
  create mode 100644 
gcc/testsuite/g++.dg/contracts/cpp26/definition-checks/contract-assert-no-def-check.C
  create mode 100644 gcc/testsuite/g++.dg/contracts/cpp26/non-trivial-ice.C

diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 245d18d30db..8b94ab77454 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1936,6 +1936,35 @@ C++ ObjC++ Var(flag_contracts_conservative_ipa) Init(1)
  -fcontracts-conservative-ipa  Do not allow certain optimisations between
  functions in contract assertions.
+Enum
+Name(client_contract_check) Type(int)  UnknownError(unrecognized client 
contract check option %qs)
+
+EnumValue
+Enum(client_contract_check) String(none) Value(0)
+
+EnumValue
+Enum(client_contract_check) String(pre) Value(1)
+
+EnumValue
+Enum(client_contract_check) String(all) Value(2)
+
+fcontracts-client-check=
+C++ ObjC++ Joined RejectNegative Enum(client_contract_check) 
Var(flag_contract_client_check) Init (0)
+-fcontracts-client-check=[none|pre|all] Select which contracts will be checked 
on the client side for non virtual functions
+
+Enum
+Name(on_off) Type(int) UnknownError(argument %qs must be either %<on%> or 
%<off%>)
+
+EnumValue
+Enum(on_off) String(off) Value(0)
+
+EnumValue
+Enum(on_off) String(on) Value(1)
+
+fcontracts-definition-check=
+C++ ObjC++ Joined RejectNegative Enum(on_off) 
Var(flag_contracts_definition_check) Init(1)
+-fcontracts-definition-check=[on|off]  Enable or disable contract checks on 
the definition side for all functions (default on).
+
  fcoroutines
  C++ ObjC++ LTO Var(flag_coroutines)
  Enable C++ coroutines (experimental).
diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index 3f0d54162fd..c697cd3699e 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -11583,7 +11583,7 @@ build_cxx_call (tree fn, int nargs, tree *argarray,
      }
if (VOID_TYPE_P (TREE_TYPE (fn)))
-    return fn;
+    return maybe_contract_wrap_call (fndecl, fn);
/* 5.2.2/11: If a function call is a prvalue of object type: if the
       function call is either the operand of a decltype-specifier or the
@@ -11595,6 +11595,7 @@ build_cxx_call (tree fn, int nargs, tree *argarray,
        fn = require_complete_type (fn, complain);
        if (fn == error_mark_node)
        return error_mark_node;
+      fn = maybe_contract_wrap_call (fndecl, fn);
if (MAYBE_CLASS_TYPE_P (TREE_TYPE (fn)))
        {
@@ -11602,6 +11603,8 @@ build_cxx_call (tree fn, int nargs, tree *argarray,
          maybe_warn_parm_abi (TREE_TYPE (fn), loc);
        }
      }
+  else
+    fn = maybe_contract_wrap_call (fndecl, fn);
    return convert_from_reference (fn);
  }
diff --git a/gcc/cp/contracts.cc b/gcc/cp/contracts.cc
index 7adfb4aa4c4..22c57b8b804 100644
--- a/gcc/cp/contracts.cc
+++ b/gcc/cp/contracts.cc
@@ -345,13 +345,6 @@ retain_decl (tree decl, copy_body_data *)
    return decl;
  }
-enum contract_match_kind
-{
-  cmk_pre,
-  cmk_post,
-  cmk_all
-};
-
  /* Copy all contracts from ATTR and apply them to FNDECL. */
static tree
@@ -844,6 +837,8 @@ build_contract_condition_function (tree fndecl, bool pre)
DECL_INITIAL (fn) = NULL_TREE;
    CONTRACT_HELPER (fn) = pre ? ldf_contract_pre : ldf_contract_post;
+  /* We might have a pre/post for a wrapper.  */
+  DECL_CONTRACT_WRAPPER (fn) = DECL_CONTRACT_WRAPPER (fndecl);
DECL_VIRTUAL_P (fn) = false; @@ -909,6 +904,173 @@ build_contract_function_decls (tree fndecl)
        set_postcondition_function (fndecl, post);
  }
+/* Map from FUNCTION_DECL to a FUNCTION_DECL for contract wrapper. */
+
+static GTY(()) hash_map<tree, tree> *decl_wrapper_fn = nullptr;
+
+/* Map from the function decl of a wrapper to the function that it wraps.  */
+
+static GTY(()) hash_map<tree, tree> *decl_for_wrapper = nullptr;
+
+/* Makes wrapper the precondition function for FNDECL.  */
+
+static void
+set_contract_wrapper_function (tree fndecl, tree wrapper)
+{
+  gcc_checking_assert (wrapper && fndecl);
+  hash_map_maybe_create<hm_ggc> (decl_wrapper_fn);
+  gcc_checking_assert (decl_wrapper_fn && !decl_wrapper_fn->get (fndecl));
+  decl_wrapper_fn->put (fndecl, wrapper);

Shorter could be

bool had = hash_map_safe_put<hm_ggc> (decl_wrapper_fn, fndecl, wrapper);
gcc_checking_assert (!had);

+
+  /* We need to know the wrapped function when composing the diagnostic.  */
+  hash_map_maybe_create<hm_ggc> (decl_for_wrapper);
+  gcc_checking_assert (decl_for_wrapper && !decl_for_wrapper->get (wrapper));
+  decl_for_wrapper->put (wrapper, fndecl);
+}
+
+/* Returns the wrapper function decl for FNDECL, or null if not set.  */
+
+static tree
+get_contract_wrapper_function (tree fndecl)
+{
+  gcc_checking_assert (fndecl);
+  tree *result = hash_map_safe_get (decl_wrapper_fn, fndecl);
+  return result ? *result : NULL_TREE;
+}
+
+static tree
+get_orig_func_for_wrapper (tree wrapper)
+{
+  gcc_checking_assert (wrapper);
+  tree *result = hash_map_safe_get (decl_for_wrapper, wrapper);
+  return result ? *result : NULL_TREE;
+}

Comment needed.

+static tree
+contracts_fixup_cdtorname (tree idin)
+{
+  const char *n = IDENTIFIER_POINTER (idin);
+  size_t l = strlen (n);

IDENTIFIER_LENGTH?

+  char *nn = xasprintf ("%.*s_", (int)l-1, n);
+  tree nid = get_identifier (nn);
+  free (nn);
+  return nid;
+}
+
+/* Build a declaration for the contract wrapper of a caller FNDECL.
+   We're making a caller side contract check wrapper. For caller side contract
+   checks, postconditions are only checked if check_post is true.
+   Defer the attachment of the contracts to this function until the callee
+   is non-dependent, or we get cases where the conditions can be non-dependent
+   but still need tsubst-ing.  */
+
+static tree
+build_contract_wrapper_function (tree fndecl)
+{
+  if (error_operand_p (fndecl))
+    return error_mark_node;
+
+  tree fnname;
+  if (DECL_NAME (fndecl) && IDENTIFIER_CDTOR_P (DECL_NAME (fndecl)))
+    fnname = contracts_fixup_cdtorname (DECL_NAME (fndecl));
+  else
+    fnname = copy_node (DECL_NAME (fndecl));

Copying an identifier doesn't make sense to me?

+  location_t loc = DECL_SOURCE_LOCATION (fndecl);
+
+  /* Handle the arg types list.  */
+  tree wrapper_args = NULL_TREE;
+  tree *last = &wrapper_args;
+  for (tree arg_type = TYPE_ARG_TYPES (TREE_TYPE (fndecl)); arg_type;
+       arg_type = TREE_CHAIN (arg_type))
+    {
+      if (arg_type == void_list_node)
+       {
+         *last = void_list_node;
+         break;
+       }
+      *last = build_tree_list (TREE_PURPOSE (arg_type), TREE_VALUE (arg_type));
+      last = &TREE_CHAIN (*last);
+    }
+
+  tree wrapper_return_type = copy_node (TREE_TYPE (TREE_TYPE (fndecl)));

Nor copying a type.

+  tree wrapper_type = build_function_type (wrapper_return_type, wrapper_args);

How (/why) is this different from TREE_TYPE (fndecl)?

+  /* Contract violation wrapper function is always noexcept. Otherwise,
+     the wrapper function is noexcept if the checked function is noexcept.  */

Always...otherwise?

We still seem to be rebuilding TREE_TYPE (fndecl).

+  if (flag_exceptions && type_noexcept_p (TREE_TYPE (fndecl)))
+    wrapper_type = build_exception_variant (wrapper_type, noexcept_true_spec);
+
+  tree wrapdecl
+    = build_lang_decl_loc (loc, FUNCTION_DECL, fnname, wrapper_type);
+
+  /* Put the wrapper in the same context as the callee.  */
+  DECL_CONTEXT (wrapdecl) = DECL_CONTEXT (fndecl);
+
+  /* This declaration is a contract wrapper function.  */
+  DECL_CONTRACT_WRAPPER (wrapdecl) = true;
+
+  DECL_SOURCE_LOCATION (wrapdecl) = loc;
+  /* The declaration was implicitly generated by the compiler.  */
+  DECL_ARTIFICIAL (wrapdecl) = true;
+  /* Declaration, no definition yet.  */
+  DECL_INITIAL (wrapdecl) = NULL_TREE;
+
+  /* Let the start function code fill in the result decl.  */
+  DECL_RESULT (wrapdecl) = NULL_TREE;
+
+  /* Copy the function parameters, if present.  Suppress (e.g. unused)
+     warnings on them.  */
+  DECL_ARGUMENTS (wrapdecl) = NULL_TREE;
+  if (tree p = DECL_ARGUMENTS (fndecl))
+    {
+      tree *last_a = &DECL_ARGUMENTS (wrapdecl);
+      for (; p; p = TREE_CHAIN (p))
+       {
+         *last_a = copy_decl (p);
+         suppress_warning (*last_a);
+         DECL_CONTEXT (*last_a) = wrapdecl;
+         last_a = &TREE_CHAIN (*last_a);
+       }
+    }
+
+  /* Copy selected attributes from the original function.  */
+  TREE_USED (wrapdecl) = TREE_USED (fndecl);
+
+  /* Copy any alignment that the FE added.  */
+  if (DECL_ALIGN (fndecl))
+    SET_DECL_ALIGN (wrapdecl, DECL_ALIGN (fndecl));
+  /* Copy any alignment the user added.  */
+  DECL_USER_ALIGN (wrapdecl) = DECL_USER_ALIGN (fndecl);

DECL_USER_ALIGN is just a flag, the actual alignment is in DECL_ALIGN. I think one comment can cover both.

+
+  /* Make this function internal.  */
+  TREE_PUBLIC (wrapdecl) = false;
+  DECL_EXTERNAL (wrapdecl) = false;
+  DECL_WEAK (wrapdecl) = false;
+
+  /* ??? copied from build_contract_condition_function.  */
+  DECL_INTERFACE_KNOWN (wrapdecl) = true;

Yes, an internal function gets DECL_INTERFACE_KNOWN because we know it's internal.

+  /* Update various inline related declaration properties.  */
+  //DECL_DECLARED_INLINE_P (wrapdecl) = true;
+  DECL_DISREGARD_INLINE_LIMITS (wrapdecl) = true;

Why do we want to require inlining?

+  suppress_warning (wrapdecl);

Which warnings?

+  return wrapdecl;
+}
+
+static tree
+get_or_create_contract_wrapper_function (tree fndecl)
+{
+  tree wrapdecl = get_contract_wrapper_function (fndecl);
+  if (!wrapdecl)
+    {
+      wrapdecl = build_contract_wrapper_function (fndecl);
+      set_contract_wrapper_function (fndecl, wrapdecl);
+    }
+  return wrapdecl;
+}
+
  void
  start_function_contracts (tree fndecl)
  {
@@ -918,6 +1080,12 @@ start_function_contracts (tree fndecl)
    if (!handle_contracts_p (fndecl))
      return;
+ /* If this is not a client side check and definition side checks are
+     disabled, do nothing.  */
+  if (!flag_contracts_definition_check
+      && !DECL_CONTRACT_WRAPPER (fndecl))
+    return;
+
    /* Even if we will use an outlined function for the check (which will be the
       same one we might use on the callee-side) we still need to check the re-
       mapped contracts for shadowing.  */
@@ -1181,6 +1349,12 @@ maybe_apply_function_contracts (tree fndecl)
         popped by our caller.  */
      return;
+ /* If this is not a client side check and definition side checks are
+     disabled, do nothing.  */
+  if (!flag_contracts_definition_check
+      && !DECL_CONTRACT_WRAPPER(fndecl))

Space before (

+    return;
+
    bool do_pre = has_active_preconditions (fndecl);
    bool do_post = has_active_postconditions (fndecl);
    /* We should not have reached here with nothing to do... */
@@ -1358,12 +1532,19 @@ remap_contract (tree src, tree dst, tree contract, bool 
duplicate_p)
     PARM_DECLs have been rewritten to the corresponding PARM_DECL in DEST.  */
tree
-copy_and_remap_contracts (tree dest, tree source)
+copy_and_remap_contracts (tree dest, tree source,
+                         contract_match_kind remap_kind)
  {
    tree last = NULL_TREE, contract_attrs = NULL_TREE;
    tree attr = get_fn_contract_specifiers (source);
    for (; attr; attr = NEXT_CONTRACT_ATTR (attr))
      {
+      if ((remap_kind == cmk_pre
+          && (TREE_CODE (CONTRACT_STATEMENT (attr)) == POSTCONDITION_STMT))
+         || (remap_kind == cmk_post
+             && (TREE_CODE (CONTRACT_STATEMENT (attr)) == PRECONDITION_STMT)))

This seems like it could be shorter to write, perhaps by adding a function taking attr and returning cmk_*, so

if (remap_kind != contract_attr_kind (attr))

+       continue;
+
        tree c = copy_node (attr);
        TREE_VALUE (c) = build_tree_list (TREE_PURPOSE (TREE_VALUE (c)),
                                        copy_node (CONTRACT_STATEMENT (c)));
@@ -1607,6 +1788,127 @@ void update_contract_arguments(tree srcdecl, tree 
destdecl)
} +/* Checks if a contract check wrapper is needed for fndecl. */
+
+static bool
+should_contract_wrap_call (bool do_pre, bool do_post)
+{
+  /* Only if the target function actually has any contracts.  */
+  if (!do_pre && !do_post)
+    return false;
+
+

Extra blank line

+  return ((flag_contract_client_check > 1)
+         || ((flag_contract_client_check > 0)
+             && do_pre));
+}
+
+/* Possibly replace call with a call to a wrapper function which
+   will do the contracts check required around a CALL to FNDECL.  */
+
+tree
+maybe_contract_wrap_call (tree fndecl, tree call)
+{
+  /* We can be called from build_cxx_call without a known callee.  */
+  if (!fndecl)
+    return call;
+
+  if (error_operand_p (fndecl) || !call || call == error_mark_node)
+    return error_mark_node;
+
+  if (!handle_contracts_p (fndecl))
+    return call;
+
+  bool do_pre = has_active_preconditions (fndecl);
+  bool do_post = has_active_postconditions (fndecl);
+
+  /* Check if we need a wrapper.  */
+  if (!should_contract_wrap_call (do_pre, do_post))
+    return call;
+
+  /* Build the declaration of the wrapper, if we need to.  */
+  tree wrapdecl = get_or_create_contract_wrapper_function (fndecl);
+
+  unsigned nargs = call_expr_nargs (call);
+  vec<tree, va_gc> *argwrap;
+  vec_alloc (argwrap, nargs);
+
+  tree arg;
+  call_expr_arg_iterator iter;
+  FOR_EACH_CALL_EXPR_ARG (arg, iter, call)
+    argwrap->quick_push (arg);
+
+  tree wrapcall = build_call_expr_loc_vec (DECL_SOURCE_LOCATION (wrapdecl),
+                                          wrapdecl, argwrap);
+
+  return wrapcall;
+}
+
+/* Map traversal callback to define a wrapper function.
+   This generates code for client-side contract check wrappers and the
+   noexcept wrapper around the contract violation handler.  */
+
+bool
+define_contract_wrapper_func (const tree& fndecl, const tree& wrapdecl, void*)
+{
+  /* If we already built this function on a previous pass, then do nothing.  */
+  if (DECL_INITIAL (wrapdecl) && DECL_INITIAL (wrapdecl) != error_mark_node)
+    return true;
+
+  /* FIXME: Maybe we should check if fndecl is still dependent?  */

No need to check here I'd think, but we might check in build_contract_wrapper_function that we aren't trying to build a wrapper for a templated function.

+  gcc_checking_assert(!DECL_HAS_CONTRACTS_P (wrapdecl));

Space before (

+  /* We check postconditions if postcondition checks are enabled for clients.
+    We should not get here unless there are some checks to make.  */
+  bool check_post = flag_contract_client_check > 1;
+  /* For wrappers on CDTORs we need to refer to the original contracts,
+     when the wrapper is around a clone.  */
+  set_fn_contract_specifiers ( wrapdecl,
+                     copy_and_remap_contracts (wrapdecl, DECL_ORIGIN (fndecl),
+                                               check_post? cmk_all : cmk_pre));
+
+  start_preparsed_function (wrapdecl, /*DECL_ATTRIBUTES*/NULL_TREE,
+                           SF_DEFAULT | SF_PRE_PARSED);
+  tree body = begin_function_body ();
+  tree compound_stmt = begin_compound_stmt (BCS_FN_BODY);
+
+  vec<tree, va_gc> * args = build_arg_list (wrapdecl);
+
+  /* We do not support contracts on virtual functions yet. Client side 
wrapping is
+   not supported for cxx2a contracts. */

No need to mention cxx2a contracts.

Jason

Reply via email to