Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions gcc/cp/call.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5121,6 +5121,7 @@ build_new_function_call (tree fn, vec<tree, va_gc> **args,
else
{
result = build_over_call (cand, LOOKUP_NORMAL, complain);
result = maybe_contract_wrap_new_method_call(cand->fn, result);
}
Copy link
Collaborator

@iains iains Sep 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder why "new" method call and not just method_call or call ? (the hiccup here is that 'new' has some specific meaning additional to the "just done" one).


if (flag_coroutines
Expand Down Expand Up @@ -5256,6 +5257,8 @@ build_operator_new_call (tree fnname, vec<tree, va_gc> **args,
/* Build the CALL_EXPR. */
tree ret = build_over_call (cand, LOOKUP_NORMAL, complain);

ret =maybe_contract_wrap_new_method_call(cand->fn, ret);

/* Set this flag for all callers of this function. In addition to
new-expressions, this is called for allocating coroutine state; treat
that as an implicit new-expression. */
Expand Down Expand Up @@ -5427,6 +5430,9 @@ build_op_call (tree obj, vec<tree, va_gc> **args, tsubst_flags_t complain)
which is operator() turns out to be a static member function,
`a' is none-the-less evaluated. */
result = keep_unused_object_arg (result, obj, cand->fn);

result = maybe_contract_wrap_new_method_call(cand->fn, result);

}
else
{
Expand Down Expand Up @@ -7292,6 +7298,7 @@ build_new_op (const op_location_t &loc, enum tree_code code, int flags,
"arguments");
}
result = build_over_call (cand, LOOKUP_NORMAL, ocomplain);
result = maybe_contract_wrap_new_method_call(cand->fn, result);
}

if (trivial_fn_p (cand->fn) || DECL_IMMEDIATE_FUNCTION_P (cand->fn))
Expand Down Expand Up @@ -7654,6 +7661,7 @@ build_op_subscript (const op_location_t &loc, tree obj,
which is operator[] turns out to be a static member function,
`a' is none-the-less evaluated. */
result = keep_unused_object_arg (result, obj, cand->fn);
result = maybe_contract_wrap_new_method_call(cand->fn, result);
}
else
gcc_unreachable ();
Expand Down Expand Up @@ -8627,6 +8635,7 @@ convert_like_internal (conversion *convs, tree expr, tree fn, int argnum,
TARGET_EXPR_LIST_INIT_P (expr) = true;
}

expr = maybe_contract_wrap_new_method_call(cand->fn, expr);
return expr;
}
case ck_identity:
Expand Down Expand Up @@ -11523,6 +11532,7 @@ complain_about_no_candidates_for_method_call (tree instance,
print_z_candidates (location_of (name), candidates);
}


/* Build a call to "INSTANCE.FN (ARGS)". If FN_P is non-NULL, it will
be set, upon return, to the function called. ARGS may be NULL.
This may change ARGS. */
Expand Down Expand Up @@ -11879,6 +11889,8 @@ build_new_method_call (tree instance, tree fns, vec<tree, va_gc> **args,
"operator delete(~X(f()))" (rather than generating
"t = f(), ~X(t), operator delete (t)"). */
call = build_nop (void_type_node, call);

call = maybe_contract_wrap_new_method_call(cand->fn, call);
}
}
}
Expand Down
236 changes: 232 additions & 4 deletions gcc/cp/contracts.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1512,7 +1512,8 @@ handle_contracts_p (tree decl1)
{
return (flag_contracts
&& !processing_template_decl
&& DECL_ABSTRACT_ORIGIN (decl1) == NULL_TREE
&& (DECL_ABSTRACT_ORIGIN (decl1) == NULL_TREE
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we really need to disentangle this from the DECL_ABSTRACT_ORIGIN (apparent) mis-use - unless the documentation for that is out of date - we should check with the GCC C++ gurus .. ... if the use of DECL_ABSTRACT_ORIGIN is an (ab)-use then that should be fixed (but not part of this patch FAOD)

|| DECL_IS_WRAPPER_FN_P(decl1))
&& contract_any_active_p (DECL_CONTRACTS (decl1)));
}

Expand Down Expand Up @@ -2004,7 +2005,8 @@ start_function_contracts (tree decl1)

/* Contracts may have just been added without a chance to parse them, though
we still need the PRE_FN available to generate a call to it. */
if (!DECL_PRE_FN (decl1))
/* Do we already have declarations generated ? */
if (!DECL_PRE_FN (decl1) && !DECL_POST_FN (decl1))
build_contract_function_decls (decl1);

}
Expand Down Expand Up @@ -2191,7 +2193,7 @@ finish_function_contracts (tree fndecl)
if (post)
{
DECL_PENDING_INLINE_P (post) = false;
start_preparsed_function (post,
start_preparsed_function (post,
DECL_ATTRIBUTES (post),
flags);
remap_and_emit_conditions (fndecl, post, POSTCONDITION_STMT);
Expand All @@ -2201,6 +2203,12 @@ finish_function_contracts (tree fndecl)
tree finished_post = finish_function (false);
expand_or_defer_fn (finished_post);
}

/* Check if we need to update wrapper function contracts. */
tree wrapdecl = get_contract_wrapper_function (fndecl);
if (wrapdecl){
copy_and_remap_contracts (wrapdecl, fndecl);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there some action needed if the wrapdecl is error_mark_node or absent?

}


Expand Down Expand Up @@ -2330,7 +2338,7 @@ duplicate_contracts (tree newdecl, tree olddecl)
PARM_DECL in OVERRIDER. */

void
inherit_base_contracts (tree overrider, tree basefn)
copy_and_remap_contracts (tree overrider, tree basefn)
{
tree last = NULL_TREE, contract_attrs = NULL_TREE;
for (tree a = DECL_CONTRACTS (basefn);
Expand All @@ -2357,4 +2365,224 @@ inherit_base_contracts (tree overrider, tree basefn)
set_decl_contracts (overrider, contract_attrs);
}

/* Build a declaration for the contract wrapper of a caller FNDECL. */

static tree
build_contract_wrapper_function (tree fndecl)
{
if (TREE_TYPE (fndecl) == error_mark_node)
return error_mark_node;

/* Use mangled name so we can differentiate between different virtual
functions of the same name. */
const char *name = IDENTIFIER_POINTER ((DECL_ASSEMBLER_NAME (fndecl)));
name = ACONCAT ((name, ".contract_wrapper", NULL));

location_t loc = DECL_SOURCE_LOCATION (fndecl);

tree wrapper_return_type = TREE_TYPE (TREE_TYPE (fndecl));
tree wrapper_args = TYPE_ARG_TYPES (TREE_TYPE (fndecl));

tree wrapper_type = build_function_type ( wrapper_return_type,
wrapper_args);


/* this will create a member function because fndecl is a member function,
so we will need to adjust the type later */
tree wrapdecl
= build_lang_decl_loc (loc, FUNCTION_DECL, get_identifier (name), wrapper_type);


DECL_CONTEXT (wrapdecl) = NULL_TREE;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we will add this later, I guess?

DECL_SOURCE_LOCATION (wrapdecl) = loc;
/*the declaration was implicitly generated by the compiler */
DECL_ARTIFICIAL (wrapdecl) = true;
/* no function body at present * */
DECL_INITIAL (wrapdecl) = error_mark_node;


if (DECL_RESULT (fndecl))
{
DECL_RESULT (wrapdecl) = copy_decl (DECL_RESULT (fndecl));
DECL_CONTEXT (DECL_RESULT (wrapdecl)) = wrapdecl;
}

/* Copy the function parameters. */
tree last = DECL_ARGUMENTS (wrapdecl) = copy_decl (DECL_ARGUMENTS (fndecl));
DECL_CONTEXT (last) = wrapdecl;
for (tree p = TREE_CHAIN (DECL_ARGUMENTS (fndecl)); p; p = TREE_CHAIN (p))
{
if (VOID_TYPE_P (p))
{
TREE_CHAIN (last) = void_list_node;
break;
}
last = TREE_CHAIN (last) = copy_decl (p);
DECL_CONTEXT (last) = wrapdecl;
}

/* Copy selected attributes from the original function. */
TREE_USED (wrapdecl) = TREE_USED (fndecl);
if (DECL_SECTION_NAME (fndecl))
set_decl_section_name (wrapdecl, 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);

/* Apply attributes from the original fn.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if any non-contracts attributes are relevant here?

TODO : we can probably skip this step if function needs
instantiating as it will be done as a part of instantiation. */
copy_and_remap_contracts (wrapdecl, fndecl);

DECL_ABSTRACT_ORIGIN (wrapdecl) = fndecl;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still feel we're abusing this (according to the doc. - it should be on our TODO to establish if the abuse is real or the doc is out of date/insufficient)


/* Make this function internal */
TREE_PUBLIC (wrapdecl) = false;
DECL_EXTERNAL (wrapdecl) = false;
DECL_WEAK (wrapdecl) = false;

// below is copied from build_contract_condition_function
DECL_INTERFACE_KNOWN (wrapdecl) = true;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what this does .. (not saying it's wrong, just not sure of the implications)


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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not disagreeing with this - but note that it is a big hammer.

TREE_NO_WARNING (wrapdecl) = 1;

return wrapdecl;
}


/* Map from FUNCTION_DECL to a FUNCTION_DECL for contract wrapper. */
static GTY(()) hash_map<tree, tree> *decl_wrapper_fn;

/* Returns the wrapper funtion for D, or null if not set. */

tree
get_contract_wrapper_function (tree decl)
{
hash_map_maybe_create<hm_ggc> (decl_wrapper_fn);
tree *result = decl_wrapper_fn->get (decl);
return result ? *result : NULL_TREE;
}

/* Makes wrapper the precondition function for D. */

void
set_contract_wrapper_function (tree decl, tree wrapper)
{
gcc_assert (wrapper);
hash_map_maybe_create<hm_ggc> (decl_wrapper_fn);
gcc_assert (!decl_wrapper_fn->get (decl));
decl_wrapper_fn->put (decl, wrapper);
}


tree
maybe_contract_wrap_new_method_call (tree fndecl, tree call)
{

if (fndecl == error_mark_node) return fndecl;

/* We only need to wrap the function if it's a virtual function */
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like us (informally) to see how tricky it would be to have a flag that allows us to apply caller-side contracts generally - it would help (perhaps) with some of the SG21 + coroutines discussion.

if (!flag_contracts_nonattr || !handle_contracts_p (fndecl)
|| !DECL_VIRTUAL_P (fndecl))
return call;

bool do_pre = has_active_preconditions (fndecl);
bool do_post = has_active_postconditions (fndecl);

/* We should not have reached here with nothing to do... */
gcc_checking_assert (do_pre || do_post);

/* build the declaration of the wrapper if we need to */
tree wrapdecl = get_contract_wrapper_function (fndecl);

if (!wrapdecl){
wrapdecl = build_contract_wrapper_function (fndecl);
wrapdecl = pushdecl_top_level (wrapdecl);
set_contract_wrapper_function (fndecl, wrapdecl);
}

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);

CALL_FROM_THUNK_P (wrapcall) = true;
return wrapcall;
}


bool define_contract_wrapper_func(const tree& fndecl, const tree& wrapdecl, void*)
{

start_preparsed_function (wrapdecl, /*DECL_ATTRIBUTES (wrapdecl)*/ 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);
tree return_type = TREE_TYPE (TREE_TYPE (fndecl));
tree *class_ptr = args->begin();
tree fn;
gcc_assert (class_ptr);

tree t;
tree binfo = lookup_base (TREE_TYPE (TREE_TYPE (*class_ptr)),
DECL_CONTEXT (fndecl),
ba_any, NULL, tf_warning_or_error);
gcc_assert (binfo && binfo != error_mark_node);

*class_ptr = build_base_path (PLUS_EXPR, *class_ptr, binfo, 1,
tf_warning_or_error);
if (TREE_SIDE_EFFECTS (*class_ptr))
*class_ptr = save_expr (*class_ptr);
t = build_pointer_type (TREE_TYPE (fndecl));
fn = build_vfn_ref (*class_ptr, DECL_VINDEX (fndecl));
TREE_TYPE (fn) = t;


tree call = build_call_a (fn,
args->length (),
args->address ());

if (!VOID_TYPE_P (return_type))
{
tree r = build_cplus_new(return_type, call, tf_warning_or_error);
finish_return_stmt (r);
}
else {
finish_return_stmt (NULL_TREE);
}
finish_compound_stmt (compound_stmt);
finish_function_body (body);
expand_or_defer_fn (finish_function (/*inline_p=*/false));
return true;
}

void emit_contract_wrapper_func()
{
if (!decl_wrapper_fn || decl_wrapper_fn->is_empty ()) return;

decl_wrapper_fn->traverse<void *, define_contract_wrapper_func>(NULL);

decl_wrapper_fn->empty ();

}



#include "gt-cp-contracts.h"
14 changes: 13 additions & 1 deletion gcc/cp/contracts.h
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,11 @@ enum contract_matching_context
#define DECL_POST_FN(NODE) \
(get_postcondition_function ((NODE)))

/* For a FUNCTION_DECL of a guarded function, this holds the function decl
where caller contract checks are emitted. */
#define DECL_WRAPPER_FN(NODE) \
(get_contract_wrapper_function ((NODE)))

/* True iff the FUNCTION_DECL is the pre function for a guarded function. */
#define DECL_IS_PRE_FN_P(NODE) \
(DECL_ABSTRACT_ORIGIN (NODE) && DECL_PRE_FN (DECL_ABSTRACT_ORIGIN (NODE)) == NODE)
Expand All @@ -273,6 +278,12 @@ enum contract_matching_context
#define DECL_IS_POST_FN_P(NODE) \
(DECL_ABSTRACT_ORIGIN (NODE) && DECL_POST_FN (DECL_ABSTRACT_ORIGIN (NODE)) == NODE)

/* True iff the FUNCTION_DECL is the caller contract wrapper function
for a guarded function. */
#define DECL_IS_WRAPPER_FN_P(NODE) \
(DECL_ABSTRACT_ORIGIN (NODE) && DECL_WRAPPER_FN (DECL_ABSTRACT_ORIGIN (NODE)) == NODE)


extern void remove_contract_attributes (tree);
extern void copy_contract_attributes (tree, tree);
extern void remap_contracts (tree, tree, tree, bool);
Expand All @@ -281,6 +292,7 @@ extern void rebuild_postconditions (tree);
extern bool check_postcondition_result (tree, tree, location_t);
extern tree get_precondition_function (tree);
extern tree get_postcondition_function (tree);
extern tree get_contract_wrapper_function (tree);
extern void duplicate_contracts (tree, tree);
extern void match_deferred_contracts (tree);
extern void defer_guarded_contract_match (tree, tree, tree);
Expand All @@ -290,7 +302,7 @@ extern tree invalidate_contract (tree);
extern void update_late_contract (tree, tree, tree);
extern tree splice_out_contracts (tree);
extern bool all_attributes_are_contracts_p (tree);
extern void inherit_base_contracts (tree, tree);
extern void copy_and_remap_contracts (tree, tree);
//extern tree apply_postcondition_to_return (tree);
extern void start_function_contracts (tree);
extern void maybe_apply_function_contracts (tree);
Expand Down
2 changes: 2 additions & 0 deletions gcc/cp/cp-tree.h
Original file line number Diff line number Diff line change
Expand Up @@ -8789,6 +8789,8 @@ extern tree grok_contract (tree, tree, tree, cp_expr, location_t);
extern tree finish_contract_condition (cp_expr);
extern tree constify_contract_access (tree);
extern tree view_as_const (tree);
extern tree maybe_contract_wrap_new_method_call (tree, tree);
extern void emit_contract_wrapper_func ();

/* Return the first contract in ATTRS, or NULL_TREE if there are none. */

Expand Down
Loading