/* Definitions for C++ contract levels
Copyright (C) 2020-2023 Free Software Foundation, Inc.
Contributed by Jeff Chapman II (jchapman@lock3software.com)
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GCC is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3. If not see
. */
/* Design Notes
A function is called a "guarded" function if it has pre or post contract
attributes. A contract is considered an "active" contract if runtime code is
needed for the contract under the current contract configuration.
pre and post contract attributes are parsed and stored in DECL_ATTRIBUTES.
assert contracts are parsed and wrapped in statements. When genericizing, all
active and assumed contracts are transformed into an if block. An observed
contract:
[[ pre: v > 0 ]]
is transformed into:
if (!(v > 0)) {
handle_contract_violation(__pseudo_contract_violation{
5, // line_number,
"main.cpp", // file_name,
"fun", // function_name,
"v > 0", // comment,
"default", // assertion_level,
"default", // assertion_role,
maybe_continue, // continuation_mode
});
terminate (); // if never_continue
}
We use an internal type with the same layout as contract_violation rather
than try to define the latter internally and somehow deal with its actual
definition in a TU that includes .
??? is it worth factoring out the calls to handle_contract_violation and
terminate into a local function?
Assumed contracts use the same implementation as C++23 [[assume]].
Parsing of pre and post contract conditions need to be deferred when the
contracts are attached to a member function. The postcondition identifier
cannot be used before the deduced return type of an auto function is used,
except when used in a defining declaration in which case they conditions are
fully parsed once the body is finished (see cpp2a/contracts-deduced{1,2}.C).
A list of pre and post contracts can either be repeated in their entirety or
completely absent in subsequent declarations. If contract lists appear on two
matching declarations, their contracts have to be equivalent. In general this
means that anything before the colon have to be token equivalent and the
condition must be cp_tree_equal (primarily to allow for parameter renaming).
Contracts on overrides must match those present on (all of) the overridee(s).
Template specializations may have their own contracts. If no contracts are
specified on the initial specialization they're assumed to be the same as
the primary template. Specialization redeclarations must then match either
the primary template (if they were unspecified originally), or those
specified on the specialization.
For non-cdtors two functions are generated for ease of implementation and to
avoid some cases where code bloat may occurr. These are the DECL_PRE_FN and
DECL_POST_FN. Each handles checking either the set of pre or post contracts
of a guarded function.
int fun(int v)
[[ pre: v > 0 ]]
[[ post r: r < 0 ]]
{
return -v;
}
The original decl is left alone and instead calls are generated to pre/post
functions within the body:
void fun.pre(int v)
{
[[ assert: v > 0 ]];
}
int fun.post(int v, int __r)
{
[[ assert: __r < 0 ]];
return __r;
}
int fun(int v)
{
fun.pre(v);
return fun.post(v, -v);
}
If fun returns in memory, the return value is not passed through the post
function; instead, the return object is initialized directly and then passed
to the post function by invisible reference.
This sides steps a number of issues with having to rewrite the bodies or
rewrite the parsed conditions as the parameters to the original function
changes (as happens during redeclaration). The ultimate goal is to get
something that optimizes well along the lines of
int fun(int v)
{
[[ assert: v > 0 ]];
auto &&__r = -v;
goto out;
out:
[[ assert: __r < 0 ]];
return __r;
}
With the idea being that multiple return statements could collapse the
function epilogue after inlining the pre/post functions. clang is able
to collapse common function epilogues, while gcc needs -O3 -Os combined.
Directly laying the pre contracts down in the function body doesn't have
many issues. The post contracts may need to be repeated multiple times, once
for each return, or a goto epilogue would need to be generated.
For this initial implementation, generating function calls and letting
later optimizations decide whether to inline and duplicate the actual
checks or whether to collapse the shared epilogue was chosen.
For cdtors a post contract is implemented using a CLEANUP_STMT.
FIXME the compiler already shores cleanup code on multiple exit paths, so
this outlining seems unnecessary if we represent the postcondition as a
cleanup for all functions.
More helpful for optimization might be to make the contracts a wrapper
function (for non-variadic functions), that could be inlined into a
caller while preserving the call to the actual function? Either that or
mirror a never-continue post contract with an assume in the caller. */
#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "cp-tree.h"
#include "stringpool.h"
#include "diagnostic.h"
#include "options.h"
#include "contracts.h"
#include "tree.h"
#include "tree-inline.h"
#include "attribs.h"
#include "tree-iterator.h"
#include "print-tree.h"
#include "stor-layout.h"
#include "intl.h"
const int max_custom_roles = 32;
static contract_role contract_build_roles[max_custom_roles] = {
};
bool valid_configs[CCS_MAYBE + 1][CCS_MAYBE + 1] = {
{ 0, 0, 0, 0, 0, },
{ 0, 1, 0, 0, 0, },
{ 0, 1, 1, 1, 1, },
{ 0, 1, 1, 1, 1, },
{ 0, 1, 0, 0, 1, },
};
void
validate_contract_role (contract_role *role)
{
gcc_assert (role);
if (!unchecked_contract_p (role->axiom_semantic))
error ("axiom contract semantic must be % or %");
if (!valid_configs[role->default_semantic][role->audit_semantic] )
warning (0, "the % semantic should be at least as strong as "
"the % semantic");
}
contract_semantic
lookup_concrete_semantic (const char *name)
{
if (strcmp (name, "ignore") == 0)
return CCS_IGNORE;
if (strcmp (name, "assume") == 0)
return CCS_ASSUME;
if (strcmp (name, "check_never_continue") == 0
|| strcmp (name, "never") == 0
|| strcmp (name, "abort") == 0)
return CCS_NEVER;
if (strcmp (name, "check_maybe_continue") == 0
|| strcmp (name, "maybe") == 0)
return CCS_MAYBE;
error ("'%s' is not a valid explicit concrete semantic", name);
return CCS_INVALID;
}
/* Compare role and name up to either the NUL terminator or the first
occurrence of colon. */
static bool
role_name_equal (const char *role, const char *name)
{
size_t role_len = strcspn (role, ":");
size_t name_len = strcspn (name, ":");
if (role_len != name_len)
return false;
return strncmp (role, name, role_len) == 0;
}
static bool
role_name_equal (contract_role *role, const char *name)
{
if (role->name == NULL)
return false;
return role_name_equal (role->name, name);
}
contract_role *
get_contract_role (const char *name)
{
for (int i = 0; i < max_custom_roles; ++i)
{
contract_role *potential = contract_build_roles + i;
if (role_name_equal (potential, name))
return potential;
}
if (role_name_equal (name, "default") || role_name_equal (name, "review"))
{
setup_default_contract_role (false);
return get_contract_role (name);
}
return NULL;
}
contract_role *
add_contract_role (const char *name,
contract_semantic des,
contract_semantic aus,
contract_semantic axs,
bool update)
{
for (int i = 0; i < max_custom_roles; ++i)
{
contract_role *potential = contract_build_roles + i;
if (potential->name != NULL
&& !role_name_equal (potential, name))
continue;
if (potential->name != NULL && !update)
return potential;
potential->name = name;
potential->default_semantic = des;
potential->audit_semantic = aus;
potential->axiom_semantic = axs;
return potential;
}
return NULL;
}
enum contract_build_level { OFF, DEFAULT, AUDIT };
static bool flag_contract_continuation_mode = false;
static bool flag_contract_assumption_mode = true;
static int flag_contract_build_level = DEFAULT;
static bool contracts_p1332_default = false, contracts_p1332_review = false,
contracts_std = false, contracts_p1429 = false;
static contract_semantic
get_concrete_check ()
{
return flag_contract_continuation_mode ? CCS_MAYBE : CCS_NEVER;
}
static contract_semantic
get_concrete_axiom_semantic ()
{
return flag_contract_assumption_mode ? CCS_ASSUME : CCS_IGNORE;
}
void
setup_default_contract_role (bool update)
{
contract_semantic check = get_concrete_check ();
contract_semantic axiom = get_concrete_axiom_semantic ();
switch (flag_contract_build_level)
{
case OFF:
add_contract_role ("default", CCS_IGNORE, CCS_IGNORE, axiom, update);
add_contract_role ("review", CCS_IGNORE, CCS_IGNORE, CCS_IGNORE, update);
break;
case DEFAULT:
add_contract_role ("default", check, CCS_IGNORE, axiom, update);
add_contract_role ("review", check, CCS_IGNORE, CCS_IGNORE, update);
break;
case AUDIT:
add_contract_role ("default", check, check, axiom, update);
add_contract_role ("review", check, check, CCS_IGNORE, update);
break;
}
}
contract_semantic
map_contract_semantic (const char *ident)
{
if (strcmp (ident, "ignore") == 0)
return CCS_IGNORE;
else if (strcmp (ident, "assume") == 0)
return CCS_ASSUME;
else if (strcmp (ident, "check_never_continue") == 0)
return CCS_NEVER;
else if (strcmp (ident, "check_maybe_continue") == 0)
return CCS_MAYBE;
return CCS_INVALID;
}
contract_level
map_contract_level (const char *ident)
{
if (strcmp (ident, "default") == 0)
return CONTRACT_DEFAULT;
else if (strcmp (ident, "audit") == 0)
return CONTRACT_AUDIT;
else if (strcmp (ident, "axiom") == 0)
return CONTRACT_AXIOM;
return CONTRACT_INVALID;
}
void
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");
return;
}
else
contracts_std = true;
if (strcmp (arg, "off") == 0)
flag_contract_build_level = OFF;
else if (strcmp (arg, "default") == 0)
flag_contract_build_level = DEFAULT;
else if (strcmp (arg, "audit") == 0)
flag_contract_build_level = AUDIT;
else
error ("%<-fcontract-build-level=%> must be off|default|audit");
setup_default_contract_role ();
}
void
handle_OPT_fcontract_assumption_mode_ (const char *arg)
{
if (contracts_p1332_default || contracts_p1332_review || contracts_p1429)
{
error ("%<-fcontract-assumption-mode=%> cannot be mixed with p1332/p1429");
return;
}
else
contracts_std = true;
if (strcmp (arg, "on") == 0)
flag_contract_assumption_mode = true;
else if (strcmp (arg, "off") == 0)
flag_contract_assumption_mode = false;
else
error ("%<-fcontract-assumption-mode=%> must be % or %");
setup_default_contract_role ();
}
void
handle_OPT_fcontract_continuation_mode_ (const char *arg)
{
if (contracts_p1332_default || contracts_p1332_review || contracts_p1429)
{
error ("%<-fcontract-continuation-mode=%> cannot be mixed with p1332/p1429");
return;
}
else
contracts_std = true;
if (strcmp (arg, "on") == 0)
flag_contract_continuation_mode = true;
else if (strcmp (arg, "off") == 0)
flag_contract_continuation_mode = false;
else
error ("%<-fcontract-continuation-mode=%> must be % or %");
setup_default_contract_role ();
}
void
handle_OPT_fcontract_role_ (const char *arg)
{
const char *name = arg;
const char *vals = strchr (name, ':');
if (vals == NULL)
{
error ("%<-fcontract-role=%> must be in the form role:semantics");
return;
}
contract_semantic dess = CCS_INVALID, auss = CCS_INVALID, axss = CCS_INVALID;
char *des = NULL, *aus = NULL, *axs = NULL;
des = xstrdup (vals + 1);
aus = strchr (des, ',');
if (aus == NULL)
{
error ("%<-fcontract-role=%> semantics must include default,audit,axiom values");
goto validate;
}
*aus = '\0'; // null terminate des
aus = aus + 1; // move past null
axs = strchr (aus, ',');
if (axs == NULL)
{
error ("%<-fcontract-role=%> semantics must include default,audit,axiom values");
goto validate;
}
*axs = '\0'; // null terminate aus
axs = axs + 1; // move past null
dess = lookup_concrete_semantic (des);
auss = lookup_concrete_semantic (aus);
axss = lookup_concrete_semantic (axs);
validate:
free (des);
if (dess == CCS_INVALID || auss == CCS_INVALID || axss == CCS_INVALID)
return;
bool is_defalult_role = role_name_equal (name, "default");
bool is_review_role = role_name_equal (name, "review");
bool is_std_role = is_defalult_role || is_review_role;
if ((contracts_std && is_std_role) || (contracts_p1429 && is_defalult_role))
{
error ("%<-fcontract-role=%> cannot be mixed with std/p1429 contract flags");
return;
}
else if (is_std_role)
{
contracts_p1332_default |= is_defalult_role;
contracts_p1332_review |= is_review_role;
}
contract_role *role = add_contract_role (name, dess, auss, axss);
if (role == NULL)
{
// TODO: not enough space?
error ("%<-fcontract-level=%> too many custom roles");
return;
}
else
validate_contract_role (role);
}
void
handle_OPT_fcontract_semantic_ (const char *arg)
{
if (!strchr (arg, ':'))
{
error ("%<-fcontract-semantic=%> must be in the form level:semantic");
return;
}
if (contracts_std || contracts_p1332_default)
{
error ("%<-fcontract-semantic=%> cannot be mixed with std/p1332 contract flags");
return;
}
contracts_p1429 = true;
contract_role *role = get_contract_role ("default");
if (!role)
{
error ("%<-fcontract-semantic=%> cannot find default role");
return;
}
const char *semantic = strchr (arg, ':') + 1;
contract_semantic sem = lookup_concrete_semantic (semantic);
if (sem == CCS_INVALID)
return;
if (strncmp ("default:", arg, 8) == 0)
role->default_semantic = sem;
else if (strncmp ("audit:", arg, 6) == 0)
role->audit_semantic = sem;
else if (strncmp ("axiom:", arg, 6) == 0)
role->axiom_semantic = sem;
else
error ("%<-fcontract-semantic=%> level must be default, audit, or axiom");
validate_contract_role (role);
}
/* Convert a contract CONFIG into a contract_mode. */
static contract_mode
contract_config_to_mode (tree config)
{
if (config == NULL_TREE)
return contract_mode (CONTRACT_DEFAULT, get_default_contract_role ());
/* TREE_LIST has TREE_VALUE is a level and TREE_PURPOSE is role. */
if (TREE_CODE (config) == TREE_LIST)
{
contract_role *role = NULL;
if (TREE_PURPOSE (config))
role = get_contract_role (IDENTIFIER_POINTER (TREE_PURPOSE (config)));
if (!role)
role = get_default_contract_role ();
contract_level level =
map_contract_level (IDENTIFIER_POINTER (TREE_VALUE (config)));
return contract_mode (level, role);
}
/* Literal semantic. */
gcc_assert (TREE_CODE (config) == IDENTIFIER_NODE);
contract_semantic semantic =
map_contract_semantic (IDENTIFIER_POINTER (config));
return contract_mode (semantic);
}
/* Convert a contract's config into a concrete semantic using the current
contract semantic mapping. */
static contract_semantic
compute_concrete_semantic (tree contract)
{
contract_mode mode = contract_config_to_mode (CONTRACT_MODE (contract));
/* Compute the concrete semantic for the contract. */
if (!flag_contract_mode)
/* If contracts are off, treat all contracts as ignore. */
return CCS_IGNORE;
else if (mode.kind == contract_mode::cm_invalid)
return CCS_INVALID;
else if (mode.kind == contract_mode::cm_explicit)
return mode.get_semantic ();
else
{
gcc_assert (mode.get_role ());
gcc_assert (mode.get_level () != CONTRACT_INVALID);
contract_level level = mode.get_level ();
contract_role *role = mode.get_role ();
if (level == CONTRACT_DEFAULT)
return role->default_semantic;
else if (level == CONTRACT_AUDIT)
return role->audit_semantic;
else if (level == CONTRACT_AXIOM)
return role->axiom_semantic;
}
gcc_assert (false);
}
/* Return true if any contract in CONTRACT_ATTRs is not yet parsed. */
bool
contract_any_deferred_p (tree contract_attr)
{
for (; contract_attr; contract_attr = CONTRACT_CHAIN (contract_attr))
if (CONTRACT_CONDITION_DEFERRED_P (CONTRACT_STATEMENT (contract_attr)))
return true;
return false;
}
/* Returns true if all attributes are contracts. */
bool
all_attributes_are_contracts_p (tree attributes)
{
for (; attributes; attributes = TREE_CHAIN (attributes))
if (!cxx_contract_attribute_p (attributes))
return false;
return true;
}
/* Mark most of a contract as being invalid. */
tree
invalidate_contract (tree t)
{
if (TREE_CODE (t) == POSTCONDITION_STMT && POSTCONDITION_IDENTIFIER (t))
POSTCONDITION_IDENTIFIER (t) = error_mark_node;
CONTRACT_CONDITION (t) = error_mark_node;
CONTRACT_COMMENT (t) = error_mark_node;
return t;
}
/* Returns an invented parameter declration of the form 'TYPE ID' for the
purpose of parsing the postcondition.
We use a PARM_DECL instead of a VAR_DECL so that tsubst forces a lookup
in local specializations when we instantiate these things later. */
tree
make_postcondition_variable (cp_expr id, tree type)
{
if (id == error_mark_node)
return id;
tree decl = build_lang_decl (PARM_DECL, id, type);
DECL_ARTIFICIAL (decl) = true;
DECL_SOURCE_LOCATION (decl) = id.get_location ();
pushdecl (decl);
return decl;
}
/* As above, except that the type is unknown. */
tree
make_postcondition_variable (cp_expr id)
{
return make_postcondition_variable (id, make_auto ());
}
/* Check that the TYPE is valid for a named postcondition variable. Emit a
diagnostic if it is not. Returns TRUE if the result is OK and false
otherwise. */
bool
check_postcondition_result (tree decl, tree type, location_t loc)
{
if (VOID_TYPE_P (type))
{
error_at (loc,
DECL_CONSTRUCTOR_P (decl)
? G_("constructor does not return a value to test")
: DECL_DESTRUCTOR_P (decl)
? G_("destructor does not return a value to test")
: G_("function does not return a value to test"));
return false;
}
return true;
}
/* Instantiate each postcondition with the return type to finalize the
attribute. */
void
rebuild_postconditions (tree decl)
{
tree type = TREE_TYPE (TREE_TYPE (decl));
tree attributes = DECL_CONTRACTS (decl);
for (; attributes ; attributes = TREE_CHAIN (attributes))
{
if (!cxx_contract_attribute_p (attributes))
continue;
tree contract = TREE_VALUE (TREE_VALUE (attributes));
if (TREE_CODE (contract) != POSTCONDITION_STMT)
continue;
tree condition = CONTRACT_CONDITION (contract);
/* If any conditions are deferred, they're all deferred. Note that
we don't have to instantiate postconditions in that case because
the type is available through the declaration. */
if (TREE_CODE (condition) == DEFERRED_PARSE)
return;
tree oldvar = POSTCONDITION_IDENTIFIER (contract);
if (!oldvar)
continue;
/* Always update the context of the result variable so that it can
be remapped by remap_contracts. */
DECL_CONTEXT (oldvar) = decl;
/* If the return type is undeduced, defer until later. */
if (TREE_CODE (type) == TEMPLATE_TYPE_PARM)
return;
/* Check the postcondition variable. */
location_t loc = DECL_SOURCE_LOCATION (oldvar);
if (!check_postcondition_result (decl, type, loc))
{
invalidate_contract (contract);
continue;
}
/* "Instantiate" the result variable using the known type. Also update
the context so the inliner will actually remap this the parameter when
generating contract checks. */
tree newvar = copy_node (oldvar);
TREE_TYPE (newvar) = type;
/* Make parameters and result available for substitution. */
local_specialization_stack stack (lss_copy);
for (tree t = DECL_ARGUMENTS (decl); t != NULL_TREE; t = TREE_CHAIN (t))
register_local_identity (t);
register_local_specialization (newvar, oldvar);
++processing_contract_condition;
condition = tsubst_expr (condition, make_tree_vec (0),
tf_warning_or_error, decl);
--processing_contract_condition;
/* Update the contract condition and result. */
POSTCONDITION_IDENTIFIER (contract) = newvar;
CONTRACT_CONDITION (contract) = finish_contract_condition (condition);
}
}
static tree
build_comment (cp_expr condition)
{
/* Try to get the actual source text for the condition; if that fails pretty
print the resulting tree. */
char *str = get_source_text_between (condition.get_start (),
condition.get_finish ());
if (!str)
{
/* FIXME cases where we end up here
#line macro usage (oof)
contracts10.C
contracts11.C */
const char *str = expr_to_string (condition);
return build_string_literal (strlen (str) + 1, str);
}
tree t = build_string_literal (strlen (str) + 1, str);
free (str);
return t;
}
/* Build a contract statement. */
tree
grok_contract (tree attribute, tree mode, tree result, cp_expr condition,
location_t loc)
{
tree_code code;
if (is_attribute_p ("assert", attribute))
code = ASSERTION_STMT;
else if (is_attribute_p ("pre", attribute))
code = PRECONDITION_STMT;
else if (is_attribute_p ("post", attribute))
code = POSTCONDITION_STMT;
else
gcc_unreachable ();
/* Build the contract. The condition is added later. In the case that
the contract is deferred, result an plain identifier, not a result
variable. */
tree contract;
tree type = void_type_node;
if (code != POSTCONDITION_STMT)
contract = build3_loc (loc, code, type, mode, NULL_TREE, NULL_TREE);
else
contract = build4_loc (loc, code, type, mode, NULL_TREE, NULL_TREE, result);
/* Determine the concrete semantic. */
set_contract_semantic (contract, compute_concrete_semantic (contract));
/* If the contract is deferred, don't do anything with the condition. */
if (TREE_CODE (condition) == DEFERRED_PARSE)
{
CONTRACT_CONDITION (contract) = condition;
return contract;
}
/* Generate the comment from the original condition. */
CONTRACT_COMMENT (contract) = build_comment (condition);
/* The condition is converted to bool. */
condition = finish_contract_condition (condition);
CONTRACT_CONDITION (contract) = condition;
return contract;
}
/* Build the contract attribute specifier where IDENTIFIER is one of 'pre',
'post' or 'assert' and CONTRACT is the underlying statement. */
tree
finish_contract_attribute (tree identifier, tree contract)
{
if (contract == error_mark_node)
return error_mark_node;
tree attribute = build_tree_list (build_tree_list (NULL_TREE, identifier),
build_tree_list (NULL_TREE, contract));
/* Mark the attribute as dependent if the condition is dependent.
TODO: I'm not sure this is strictly necessary. It's going to be marked as
such by a subroutine of cplus_decl_attributes. */
tree condition = CONTRACT_CONDITION (contract);
if (TREE_CODE (condition) == DEFERRED_PARSE
|| value_dependent_expression_p (condition))
ATTR_IS_DEPENDENT (attribute) = true;
return attribute;
}
/* Update condition of a late-parsed contract and postcondition variable,
if any. */
void
update_late_contract (tree contract, tree result, tree condition)
{
if (TREE_CODE (contract) == POSTCONDITION_STMT)
POSTCONDITION_IDENTIFIER (contract) = result;
/* Generate the comment from the original condition. */
CONTRACT_COMMENT (contract) = build_comment (condition);
/* The condition is converted to bool. */
condition = finish_contract_condition (condition);
CONTRACT_CONDITION (contract) = condition;
}
/* Return TRUE iff ATTR has been parsed by the front-end as a c++2a contract
attribute. */
bool
cxx_contract_attribute_p (const_tree attr)
{
if (attr == NULL_TREE
|| TREE_CODE (attr) != TREE_LIST)
return false;
if (!TREE_PURPOSE (attr) || TREE_CODE (TREE_PURPOSE (attr)) != TREE_LIST)
return false;
if (!TREE_VALUE (attr) || TREE_CODE (TREE_VALUE (attr)) != TREE_LIST)
return false;
if (!TREE_VALUE (TREE_VALUE (attr)))
return false;
return (TREE_CODE (TREE_VALUE (TREE_VALUE (attr))) == PRECONDITION_STMT
|| TREE_CODE (TREE_VALUE (TREE_VALUE (attr))) == POSTCONDITION_STMT
|| TREE_CODE (TREE_VALUE (TREE_VALUE (attr))) == ASSERTION_STMT);
}
/* True if ATTR is an assertion. */
bool
cp_contract_assertion_p (const_tree attr)
{
/* This is only an assertion if it is a valid cxx contract attribute and the
statement is an ASSERTION_STMT. */
return cxx_contract_attribute_p (attr)
&& TREE_CODE (CONTRACT_STATEMENT (attr)) == ASSERTION_STMT;
}
/* Remove all c++2a style contract attributes from the DECL_ATTRIBUTEs of the
FUNCTION_DECL FNDECL. */
void
remove_contract_attributes (tree fndecl)
{
tree list = NULL_TREE;
for (tree p = DECL_ATTRIBUTES (fndecl); p; p = TREE_CHAIN (p))
if (!cxx_contract_attribute_p (p))
list = tree_cons (TREE_PURPOSE (p), TREE_VALUE (p), list);
DECL_ATTRIBUTES (fndecl) = nreverse (list);
}
static tree find_first_non_contract (tree attributes)
{
tree head = attributes;
tree p = find_contract (attributes);
/* There are no contracts. */
if (!p)
return head;
/* There are leading contracts. */
if (p == head)
{
while (cxx_contract_attribute_p (p))
p = TREE_CHAIN (p);
head = p;
}
return head;
}
/* Remove contracts from ATTRIBUTES. */
tree splice_out_contracts (tree attributes)
{
tree head = find_first_non_contract (attributes);
if (!head)
return NULL_TREE;
/* Splice out remaining contracts. */
tree p = TREE_CHAIN (head);
tree q = head;
while (p)
{
if (cxx_contract_attribute_p (p))
{
/* Skip a sequence of contracts and then link q to the next
non-contract attribute. */
do
p = TREE_CHAIN (p);
while (cxx_contract_attribute_p (p));
TREE_CHAIN (q) = p;
}
else
p = TREE_CHAIN (p);
}
return head;
}
/* Copy contract attributes from NEWDECL onto the attribute list of OLDDECL. */
void copy_contract_attributes (tree olddecl, tree newdecl)
{
tree attrs = NULL_TREE;
for (tree c = DECL_CONTRACTS (newdecl); c; c = TREE_CHAIN (c))
{
if (!cxx_contract_attribute_p (c))
continue;
attrs = tree_cons (TREE_PURPOSE (c), TREE_VALUE (c), attrs);
}
attrs = chainon (DECL_ATTRIBUTES (olddecl), nreverse (attrs));
DECL_ATTRIBUTES (olddecl) = attrs;
/* And update DECL_CONTEXT of the postcondition result identifier. */
rebuild_postconditions (olddecl);
}
/* Returns the parameter corresponding to the return value of a guarded
function D. Returns NULL_TREE if D has no postconditions or is void. */
static tree
get_postcondition_result_parameter (tree d)
{
if (!d || d == error_mark_node)
return NULL_TREE;
if (VOID_TYPE_P (TREE_TYPE (TREE_TYPE (d))))
return NULL_TREE;
tree post = DECL_POST_FN (d);
if (!post || post == error_mark_node)
return NULL_TREE;
for (tree arg = DECL_ARGUMENTS (post); arg; arg = TREE_CHAIN (arg))
if (!TREE_CHAIN (arg))
return arg;
return NULL_TREE;
}
/* For use with the tree inliner. This preserves non-mapped local variables,
such as postcondition result variables, during remapping. */
static tree
retain_decl (tree decl, copy_body_data *)
{
return decl;
}
/* Rewrite the condition of contract in place, so that references to SRC's
parameters are updated to refer to DST's parameters. The postcondition
result variable is left unchanged.
This, along with remap_contracts, are subroutines of duplicate_decls.
When declarations are merged, we sometimes need to update contracts to
refer to new parameters.
If DUPLICATE_P is true, this is called by duplicate_decls to rewrite contacts
in terms of a new set of parameters. In this case, we can retain local
variables appearing in the contract because the contract is not being
prepared for insertion into a new function. Importantly, this preserves the
references to postcondition results, which are not replaced during merging.
If false, we're preparing to emit the contract condition into the body
of a new function, so we need to make copies of all local variables
appearing in the contract (e.g., if it includes a lambda expression). Note
that in this case, postcondition results are mapped to the last parameter
of DST.
This is also used to reuse a parent type's contracts on virtual methods. */
static void
remap_contract (tree src, tree dst, tree contract, bool duplicate_p)
{
copy_body_data id;
hash_map decl_map;
memset (&id, 0, sizeof (id));
id.src_fn = src;
id.dst_fn = dst;
id.src_cfun = DECL_STRUCT_FUNCTION (src);
id.decl_map = &decl_map;
/* If we're merging contracts, don't copy local variables. */
id.copy_decl = duplicate_p ? retain_decl : copy_decl_no_change;
id.transform_call_graph_edges = CB_CGE_DUPLICATE;
id.transform_new_cfg = false;
id.transform_return_to_modify = false;
id.transform_parameter = true;
/* Make sure not to unshare trees behind the front-end's back
since front-end specific mechanisms may rely on sharing. */
id.regimplify = false;
id.do_not_unshare = true;
id.do_not_fold = true;
/* We're not inside any EH region. */
id.eh_lp_nr = 0;
bool do_remap = false;
/* Insert parameter remappings. */
if (TREE_CODE (src) == FUNCTION_DECL)
src = DECL_ARGUMENTS (src);
if (TREE_CODE (dst) == FUNCTION_DECL)
dst = DECL_ARGUMENTS (dst);
for (tree sp = src, dp = dst;
sp || dp;
sp = DECL_CHAIN (sp), dp = DECL_CHAIN (dp))
{
if (!sp && dp
&& TREE_CODE (contract) == POSTCONDITION_STMT
&& DECL_CHAIN (dp) == NULL_TREE)
{
gcc_assert (!duplicate_p);
if (tree result = POSTCONDITION_IDENTIFIER (contract))
{
gcc_assert (DECL_P (result));
insert_decl_map (&id, result, dp);
do_remap = true;
}
break;
}
gcc_assert (sp && dp);
if (sp == dp)
continue;
insert_decl_map (&id, sp, dp);
do_remap = true;
}
if (!do_remap)
return;
walk_tree (&CONTRACT_CONDITION (contract), copy_tree_body_r, &id, NULL);
}
/* Rewrite any references to SRC's PARM_DECLs to the corresponding PARM_DECL in
DST in all of the contract attributes in CONTRACTS by calling remap_contract
on each.
This is used for two purposes: to rewrite contract attributes during
duplicate_decls, and to prepare contracts for emission into a function's
respective precondition and postcondition functions. DUPLICATE_P is used
to determine the context in which this function is called. See above for
the behavior described by this flag. */
void
remap_contracts (tree src, tree dst, tree contracts, bool duplicate_p)
{
for (tree attr = contracts; attr; attr = CONTRACT_CHAIN (attr))
{
if (!cxx_contract_attribute_p (attr))
continue;
tree contract = CONTRACT_STATEMENT (attr);
if (TREE_CODE (CONTRACT_CONDITION (contract)) != DEFERRED_PARSE)
remap_contract (src, dst, contract, duplicate_p);
}
}
/* Helper to replace references to dummy this parameters with references to
the first argument of the FUNCTION_DECL DATA. */
static tree
remap_dummy_this_1 (tree *tp, int *, void *data)
{
if (!is_this_parameter (*tp))
return NULL_TREE;
tree fn = (tree)data;
*tp = DECL_ARGUMENTS (fn);
return NULL_TREE;
}
/* Replace all references to dummy this parameters in EXPR with references to
the first argument of the FUNCTION_DECL FN. */
static void
remap_dummy_this (tree fn, tree *expr)
{
walk_tree (expr, remap_dummy_this_1, fn, NULL);
}
/* Contract matching. */
/* True if the contract is valid. */
static bool
contract_valid_p (tree contract)
{
return CONTRACT_CONDITION (contract) != error_mark_node;
}
/* True if the contract attribute is valid. */
static bool
contract_attribute_valid_p (tree attribute)
{
return contract_valid_p (TREE_VALUE (TREE_VALUE (attribute)));
}
/* Compare the contract conditions of OLD_ATTR and NEW_ATTR. Returns false
if the conditions are equivalent, and true otherwise. */
static bool
check_for_mismatched_contracts (tree old_attr, tree new_attr,
contract_matching_context ctx)
{
tree old_contract = CONTRACT_STATEMENT (old_attr);
tree new_contract = CONTRACT_STATEMENT (new_attr);
/* Different kinds of contracts do not match. */
if (TREE_CODE (old_contract) != TREE_CODE (new_contract))
{
auto_diagnostic_group d;
error_at (EXPR_LOCATION (new_contract),
ctx == cmc_declaration
? "mismatched contract attribute in declaration"
: "mismatched contract attribute in override");
inform (EXPR_LOCATION (old_contract), "previous contract here");
return true;
}
/* A deferred contract tentatively matches. */
if (CONTRACT_CONDITION_DEFERRED_P (new_contract))
return false;
/* Compare the conditions of the contracts. We fold immediately to avoid
issues comparing contracts on overrides that use parameters -- see
contracts-pre3. */
tree t1 = cp_fully_fold_init (CONTRACT_CONDITION (old_contract));
tree t2 = cp_fully_fold_init (CONTRACT_CONDITION (new_contract));
/* Compare the contracts. The fold doesn't eliminate conversions to members.
Set the comparing_override_contracts flag to ensure that references
through 'this' are equal if they designate the same member, regardless of
the path those members. */
bool saved_comparing_contracts = comparing_override_contracts;
comparing_override_contracts = (ctx == cmc_override);
bool matching_p = cp_tree_equal (t1, t2);
comparing_override_contracts = saved_comparing_contracts;
if (!matching_p)
{
auto_diagnostic_group d;
error_at (EXPR_LOCATION (CONTRACT_CONDITION (new_contract)),
ctx == cmc_declaration
? "mismatched contract condition in declaration"
: "mismatched contract condition in override");
inform (EXPR_LOCATION (CONTRACT_CONDITION (old_contract)),
"previous contract here");
return true;
}
return false;
}
/* Compare the contract attributes of OLDDECL and NEWDECL. Returns true
if the contracts match, and false if they differ. */
bool
match_contract_conditions (location_t oldloc, tree old_attrs,
location_t newloc, tree new_attrs,
contract_matching_context ctx)
{
/* Contracts only match if they are both specified. */
if (!old_attrs || !new_attrs)
return true;
/* Compare each contract in turn. */
while (old_attrs && new_attrs)
{
/* If either contract is ill-formed, skip the rest of the comparison,
since we've already diagnosed an error. */
if (!contract_attribute_valid_p (new_attrs)
|| !contract_attribute_valid_p (old_attrs))
return false;
if (check_for_mismatched_contracts (old_attrs, new_attrs, ctx))
return false;
old_attrs = CONTRACT_CHAIN (old_attrs);
new_attrs = CONTRACT_CHAIN (new_attrs);
}
/* If we didn't compare all attributes, the contracts don't match. */
if (old_attrs || new_attrs)
{
auto_diagnostic_group d;
error_at (newloc,
ctx == cmc_declaration
? "declaration has a different number of contracts than "
"previously declared"
: "override has a different number of contracts than "
"previously declared");
inform (oldloc,
new_attrs
? "original declaration with fewer contracts here"
: "original declaration with more contracts here");
return false;
}
return true;
}
/* Deferred contract mapping.
This is used to compare late-parsed contracts on overrides with their
base class functions.
TODO: It seems like this could be replaced by a simple list that maps from
overrides to their base functions. It's not clear that we really need
a map to a function + a list of contracts. */
/* Map from FNDECL to a tree list of contracts that have not been matched or
diagnosed yet. The TREE_PURPOSE is the basefn we're overriding, and the
TREE_VALUE is the list of contract attrs for BASEFN. */
static hash_map pending_guarded_decls;
void
defer_guarded_contract_match (tree fndecl, tree fn, tree contracts)
{
if (!pending_guarded_decls.get (fndecl))
{
pending_guarded_decls.put (fndecl, build_tree_list (fn, contracts));
return;
}
for (tree pending = *pending_guarded_decls.get (fndecl);
pending;
pending = TREE_CHAIN (pending))
{
if (TREE_VALUE (pending) == contracts)
return;
if (TREE_CHAIN (pending) == NULL_TREE)
TREE_CHAIN (pending) = build_tree_list (fn, contracts);
}
}
/* If the FUNCTION_DECL DECL has any contracts that had their matching
deferred earlier, do that checking now. */
void
match_deferred_contracts (tree decl)
{
tree *tp = pending_guarded_decls.get (decl);
if (!tp)
return;
gcc_assert(!contract_any_deferred_p (DECL_CONTRACTS (decl)));
processing_template_decl_sentinel ptds;
processing_template_decl = uses_template_parms (decl);
/* Do late contract matching. */
for (tree pending = *tp; pending; pending = TREE_CHAIN (pending))
{
tree new_contracts = TREE_VALUE (pending);
location_t new_loc = CONTRACT_SOURCE_LOCATION (new_contracts);
tree old_contracts = DECL_CONTRACTS (decl);
location_t old_loc = CONTRACT_SOURCE_LOCATION (old_contracts);
tree base = TREE_PURPOSE (pending);
match_contract_conditions (new_loc, new_contracts,
old_loc, old_contracts,
base ? cmc_override : cmc_declaration);
}
/* Clear out deferred match list so we don't check it twice. */
pending_guarded_decls.remove (decl);
}
/* Map from FUNCTION_DECL to a FUNCTION_DECL for either the PRE_FN or POST_FN.
These are used to parse contract conditions and are called inside the body
of the guarded function. */
static GTY(()) hash_map *decl_pre_fn;
static GTY(()) hash_map *decl_post_fn;
/* Returns the precondition funtion for D, or null if not set. */
tree
get_precondition_function (tree d)
{
hash_map_maybe_create (decl_pre_fn);
tree *result = decl_pre_fn->get (d);
return result ? *result : NULL_TREE;
}
/* Returns the postcondition funtion for D, or null if not set. */
tree
get_postcondition_function (tree d)
{
hash_map_maybe_create (decl_post_fn);
tree *result = decl_post_fn->get (d);
return result ? *result : NULL_TREE;
}
/* Makes PRE the precondition function for D. */
void
set_precondition_function (tree d, tree pre)
{
gcc_assert (pre);
hash_map_maybe_create (decl_pre_fn);
gcc_assert (!decl_pre_fn->get (d));
decl_pre_fn->put (d, pre);
}
/* Makes POST the postcondition function for D. */
void
set_postcondition_function (tree d, tree post)
{
gcc_assert (post);
hash_map_maybe_create (decl_post_fn);
gcc_assert (!decl_post_fn->get (d));
decl_post_fn->put (d, post);
}
/* Set the PRE and POST functions for D. Note that PRE and POST can be
null in this case. If so the functions are not recorded. */
void
set_contract_functions (tree d, tree pre, tree post)
{
if (pre)
set_precondition_function (d, pre);
if (post)
set_postcondition_function (d, post);
}
/* Return a copy of the FUNCTION_DECL IDECL with its own unshared
PARM_DECL and DECL_ATTRIBUTEs. */
static tree
copy_fn_decl (tree idecl)
{
tree decl = copy_decl (idecl);
DECL_ATTRIBUTES (decl) = copy_list (DECL_ATTRIBUTES (idecl));
if (DECL_RESULT (idecl))
{
DECL_RESULT (decl) = copy_decl (DECL_RESULT (idecl));
DECL_CONTEXT (DECL_RESULT (decl)) = decl;
}
if (!DECL_ARGUMENTS (idecl) || VOID_TYPE_P (DECL_ARGUMENTS (idecl)))
return decl;
tree last = DECL_ARGUMENTS (decl) = copy_decl (DECL_ARGUMENTS (decl));
DECL_CONTEXT (last) = decl;
for (tree p = TREE_CHAIN (DECL_ARGUMENTS (idecl)); 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) = decl;
}
return decl;
}
/* Build a declaration for the pre- or postcondition of a guarded FNDECL. */
static tree
build_contract_condition_function (tree fndecl, bool pre)
{
if (TREE_TYPE (fndecl) == error_mark_node)
return error_mark_node;
if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl)
&& !TYPE_METHOD_BASETYPE (TREE_TYPE (fndecl)))
return error_mark_node;
/* Create and rename the unchecked function and give an internal name. */
tree fn = copy_fn_decl (fndecl);
DECL_RESULT (fn) = NULL_TREE;
tree value_type = pre ? void_type_node : TREE_TYPE (TREE_TYPE (fn));
/* Don't propagate declaration attributes to the checking function,
including the original contracts. */
DECL_ATTRIBUTES (fn) = NULL_TREE;
tree arg_types = NULL_TREE;
tree *last = &arg_types;
/* FIXME will later optimizations delete unused args to prevent extra arg
passing? do we care? */
tree class_type = NULL_TREE;
for (tree arg_type = TYPE_ARG_TYPES (TREE_TYPE (fn));
arg_type && arg_type != void_list_node;
arg_type = TREE_CHAIN (arg_type))
{
if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl)
&& TYPE_ARG_TYPES (TREE_TYPE (fn)) == arg_type)
{
class_type = TREE_TYPE (TREE_VALUE (arg_type));
continue;
}
*last = build_tree_list (TREE_PURPOSE (arg_type), TREE_VALUE (arg_type));
last = &TREE_CHAIN (*last);
}
if (pre || VOID_TYPE_P (value_type))
*last = void_list_node;
else
{
tree name = get_identifier ("__r");
tree parm = build_lang_decl (PARM_DECL, name, value_type);
DECL_CONTEXT (parm) = fn;
DECL_ARTIFICIAL (parm) = true;
DECL_ARGUMENTS (fn) = chainon (DECL_ARGUMENTS (fn), parm);
*last = build_tree_list (NULL_TREE, value_type);
TREE_CHAIN (*last) = void_list_node;
if (aggregate_value_p (value_type, fndecl))
/* If FNDECL returns in memory, don't return the value from the
postcondition. */
value_type = void_type_node;
}
TREE_TYPE (fn) = build_function_type (value_type, arg_types);
if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl))
TREE_TYPE (fn) = build_method_type (class_type, TREE_TYPE (fn));
DECL_NAME (fn) = copy_node (DECL_NAME (fn));
DECL_INITIAL (fn) = error_mark_node;
DECL_ABSTRACT_ORIGIN (fn) = fndecl;
IDENTIFIER_VIRTUAL_P (DECL_NAME (fn)) = false;
DECL_VIRTUAL_P (fn) = false;
/* Make these functions internal if we can, i.e. if the guarded function is
not vague linkage, or if we can put them in a comdat group with the
guarded function. */
if (!DECL_WEAK (fndecl) || HAVE_COMDAT_GROUP)
{
TREE_PUBLIC (fn) = false;
DECL_EXTERNAL (fn) = false;
DECL_WEAK (fn) = false;
DECL_COMDAT (fn) = false;
/* We haven't set the comdat group on the guarded function yet, we'll add
this to the same group in comdat_linkage later. */
gcc_assert (!DECL_ONE_ONLY (fndecl));
DECL_INTERFACE_KNOWN (fn) = true;
}
DECL_ARTIFICIAL (fn) = true;
/* Update various inline related declaration properties. */
//DECL_DECLARED_INLINE_P (fn) = true;
DECL_DISREGARD_INLINE_LIMITS (fn) = true;
TREE_NO_WARNING (fn) = 1;
return fn;
}
/* Return true if CONTRACT is checked or assumed under the current build
configuration. */
bool
contract_active_p (tree contract)
{
return get_contract_semantic (contract) != CCS_IGNORE;
}
static bool
has_active_contract_condition (tree d, tree_code c)
{
for (tree as = DECL_CONTRACTS (d) ; as != NULL_TREE; as = TREE_CHAIN (as))
{
tree contract = TREE_VALUE (TREE_VALUE (as));
if (TREE_CODE (contract) == c && contract_active_p (contract))
return true;
}
return false;
}
/* True if D has any checked or assumed preconditions. */
static bool
has_active_preconditions (tree d)
{
return has_active_contract_condition (d, PRECONDITION_STMT);
}
/* True if D has any checked or assumed postconditions. */
static bool
has_active_postconditions (tree d)
{
return has_active_contract_condition (d, POSTCONDITION_STMT);
}
/* Return true if any contract in the CONTRACT list is checked or assumed
under the current build configuration. */
bool
contract_any_active_p (tree contract)
{
for (; contract != NULL_TREE; contract = CONTRACT_CHAIN (contract))
if (contract_active_p (TREE_VALUE (TREE_VALUE (contract))))
return true;
return false;
}
/* Do we need to mess with contracts for DECL1? */
static bool
handle_contracts_p (tree decl1)
{
return (flag_contracts
&& !processing_template_decl
&& DECL_ABSTRACT_ORIGIN (decl1) == NULL_TREE
&& contract_any_active_p (DECL_CONTRACTS (decl1)));
}
/* Should we break out DECL1's pre/post contracts into separate functions?
FIXME I'd like this to default to 0, but that will need an overhaul to the
return identifier handling to just refer to the RESULT_DECL. */
static bool
outline_contracts_p (tree decl1)
{
return (!DECL_CONSTRUCTOR_P (decl1)
&& !DECL_DESTRUCTOR_P (decl1));
}
/* Build the precondition checking function for D. */
static tree
build_precondition_function (tree d)
{
if (!has_active_preconditions (d))
return NULL_TREE;
return build_contract_condition_function (d, /*pre=*/true);
}
/* Build the postcondition checking function for D. If the return
type is undeduced, don't build the function yet. We do that in
apply_deduced_return_type. */
static tree
build_postcondition_function (tree d)
{
if (!has_active_postconditions (d))
return NULL_TREE;
tree type = TREE_TYPE (TREE_TYPE (d));
if (is_auto (type))
return NULL_TREE;
return build_contract_condition_function (d, /*pre=*/false);
}
static void
build_contract_function_decls (tree d)
{
/* Constructors and destructors have their contracts inserted inline. */
if (!outline_contracts_p (d))
return;
/* Build the pre/post functions (or not). */
tree pre = build_precondition_function (d);
tree post = build_postcondition_function (d);
set_contract_functions (d, pre, post);
}
static const char *
get_contract_level_name (tree contract)
{
if (CONTRACT_LITERAL_MODE_P (contract))
return "";
if (tree mode = CONTRACT_MODE (contract))
if (tree level = TREE_VALUE (mode))
return IDENTIFIER_POINTER (level);
return "default";
}
static const char *
get_contract_role_name (tree contract)
{
if (CONTRACT_LITERAL_MODE_P (contract))
return "";
if (tree mode = CONTRACT_MODE (contract))
if (tree role = TREE_PURPOSE (mode))
return IDENTIFIER_POINTER (role);
return "default";
}
/* Build a layout-compatible internal version of std::contract_violation. */
static tree
get_pseudo_contract_violation_type ()
{
if (!pseudo_contract_violation_type)
{
/* Must match :
class contract_violation {
const char* _M_file;
const char* _M_function;
const char* _M_comment;
const char* _M_level;
const char* _M_role;
uint_least32_t _M_line;
signed char _M_continue;
If this changes, also update the initializer in
build_contract_violation. */
const tree types[] = { const_string_type_node,
const_string_type_node,
const_string_type_node,
const_string_type_node,
const_string_type_node,
uint_least32_type_node,
signed_char_type_node };
tree fields = NULL_TREE;
for (tree type : types)
{
/* finish_builtin_struct wants fieldss chained in reverse. */
tree next = build_decl (BUILTINS_LOCATION, FIELD_DECL,
NULL_TREE, type);
DECL_CHAIN (next) = fields;
fields = next;
}
iloc_sentinel ils (input_location);
input_location = BUILTINS_LOCATION;
pseudo_contract_violation_type = make_class_type (RECORD_TYPE);
finish_builtin_struct (pseudo_contract_violation_type,
"__pseudo_contract_violation",
fields, NULL_TREE);
CLASSTYPE_AS_BASE (pseudo_contract_violation_type)
= pseudo_contract_violation_type;
DECL_CONTEXT (TYPE_NAME (pseudo_contract_violation_type))
= FROB_CONTEXT (global_namespace);
TREE_PUBLIC (TYPE_NAME (pseudo_contract_violation_type)) = true;
CLASSTYPE_LITERAL_P (pseudo_contract_violation_type) = true;
CLASSTYPE_LAZY_COPY_CTOR (pseudo_contract_violation_type) = true;
xref_basetypes (pseudo_contract_violation_type, /*bases=*/NULL_TREE);
pseudo_contract_violation_type
= cp_build_qualified_type (pseudo_contract_violation_type,
TYPE_QUAL_CONST);
}
return pseudo_contract_violation_type;
}
/* Return a VAR_DECL to pass to handle_contract_violation. */
static tree
build_contract_violation (tree contract, contract_continuation cmode)
{
expanded_location loc = expand_location (EXPR_LOCATION (contract));
const char *function = fndecl_name (DECL_ORIGIN (current_function_decl));
const char *level = get_contract_level_name (contract);
const char *role = get_contract_role_name (contract);
/* Must match the type layout in get_pseudo_contract_violation_type. */
tree ctor = build_constructor_va
(init_list_type_node, 7,
NULL_TREE, build_string_literal (loc.file),
NULL_TREE, build_string_literal (function),
NULL_TREE, CONTRACT_COMMENT (contract),
NULL_TREE, build_string_literal (level),
NULL_TREE, build_string_literal (role),
NULL_TREE, build_int_cst (uint_least32_type_node, loc.line),
NULL_TREE, build_int_cst (signed_char_type_node, cmode));
ctor = finish_compound_literal (get_pseudo_contract_violation_type (),
ctor, tf_none);
protected_set_expr_location (ctor, EXPR_LOCATION (contract));
return ctor;
}
/* Return handle_contract_violation(), declaring it if needed. */
static tree
declare_handle_contract_violation ()
{
tree fnname = get_identifier ("handle_contract_violation");
tree viol_name = get_identifier ("contract_violation");
tree l = lookup_qualified_name (global_namespace, fnname,
LOOK_want::HIDDEN_FRIEND);
for (tree f: lkp_range (l))
if (TREE_CODE (f) == FUNCTION_DECL)
{
tree parms = TYPE_ARG_TYPES (TREE_TYPE (f));
if (remaining_arguments (parms) != 1)
continue;
tree parmtype = non_reference (TREE_VALUE (parms));
if (CLASS_TYPE_P (parmtype)
&& TYPE_IDENTIFIER (parmtype) == viol_name)
return f;
}
tree id_exp = get_identifier ("experimental");
tree ns_exp = lookup_qualified_name (std_node, id_exp);
tree violation = error_mark_node;
if (TREE_CODE (ns_exp) == NAMESPACE_DECL)
violation = lookup_qualified_name (ns_exp, viol_name,
LOOK_want::TYPE
|LOOK_want::HIDDEN_FRIEND);
if (TREE_CODE (violation) == TYPE_DECL)
violation = TREE_TYPE (violation);
else
{
push_nested_namespace (std_node);
push_namespace (id_exp, /*inline*/false);
violation = make_class_type (RECORD_TYPE);
create_implicit_typedef (viol_name, violation);
DECL_SOURCE_LOCATION (TYPE_NAME (violation)) = BUILTINS_LOCATION;
DECL_CONTEXT (TYPE_NAME (violation)) = current_namespace;
pushdecl_namespace_level (TYPE_NAME (violation), /*hidden*/true);
pop_namespace ();
pop_nested_namespace (std_node);
}
tree argtype = cp_build_qualified_type (violation, TYPE_QUAL_CONST);
argtype = cp_build_reference_type (argtype, /*rval*/false);
tree fntype = build_function_type_list (void_type_node, argtype, NULL_TREE);
push_nested_namespace (global_namespace);
tree fn = build_cp_library_fn_ptr ("handle_contract_violation", fntype,
ECF_COLD);
pushdecl_namespace_level (fn, /*hiding*/true);
pop_nested_namespace (global_namespace);
return fn;
}
/* Build the call to handle_contract_violation for CONTRACT. */
static void
build_contract_handler_call (tree contract,
contract_continuation cmode)
{
tree violation = build_contract_violation (contract, cmode);
tree violation_fn = declare_handle_contract_violation ();
tree call = build_call_n (violation_fn, 1, build_address (violation));
finish_expr_stmt (call);
}
/* Generate the code that checks or assumes a contract, but do not attach
it to the current context. This is called during genericization. */
tree
build_contract_check (tree contract)
{
contract_semantic semantic = get_contract_semantic (contract);
if (semantic == CCS_INVALID)
return NULL_TREE;
/* Ignored contracts are never checked or assumed. */
if (semantic == CCS_IGNORE)
return void_node;
remap_dummy_this (current_function_decl, &CONTRACT_CONDITION (contract));
tree condition = CONTRACT_CONDITION (contract);
if (condition == error_mark_node)
return NULL_TREE;
location_t loc = EXPR_LOCATION (contract);
if (semantic == CCS_ASSUME)
return build_assume_call (loc, condition);
tree if_stmt = begin_if_stmt ();
tree cond = build_x_unary_op (loc,
TRUTH_NOT_EXPR,
condition, NULL_TREE,
tf_warning_or_error);
finish_if_stmt_cond (cond, if_stmt);
/* Get the continuation mode. */
contract_continuation cmode;
switch (semantic)
{
case CCS_NEVER: cmode = NEVER_CONTINUE; break;
case CCS_MAYBE: cmode = MAYBE_CONTINUE; break;
default: gcc_unreachable ();
}
build_contract_handler_call (contract, cmode);
if (cmode == NEVER_CONTINUE)
finish_expr_stmt (build_call_a (terminate_fn, 0, nullptr));
finish_then_clause (if_stmt);
tree scope = IF_SCOPE (if_stmt);
IF_SCOPE (if_stmt) = NULL;
return do_poplevel (scope);
}
/* Add the contract statement CONTRACT to the current block if valid. */
static void
emit_contract_statement (tree contract)
{
/* Only add valid contracts. */
if (get_contract_semantic (contract) != CCS_INVALID
&& CONTRACT_CONDITION (contract) != error_mark_node)
add_stmt (contract);
}
/* Generate the statement for the given contract attribute by adding the
statement to the current block. Returns the next contract in the chain. */
static tree
emit_contract_attr (tree attr)
{
gcc_assert (TREE_CODE (attr) == TREE_LIST);
emit_contract_statement (CONTRACT_STATEMENT (attr));
return CONTRACT_CHAIN (attr);
}
/* Add the statements of contract attributes ATTRS to the current block. */
static void
emit_contract_conditions (tree attrs, tree_code code)
{
if (!attrs) return;
gcc_assert (TREE_CODE (attrs) == TREE_LIST);
gcc_assert (code == PRECONDITION_STMT || code == POSTCONDITION_STMT);
while (attrs)
{
tree contract = CONTRACT_STATEMENT (attrs);
if (TREE_CODE (contract) == code)
attrs = emit_contract_attr (attrs);
else
attrs = CONTRACT_CHAIN (attrs);
}
}
/* Emit the statement for an assertion attribute. */
void
emit_assertion (tree attr)
{
emit_contract_attr (attr);
}
/* Emit statements for precondition attributes. */
static void
emit_preconditions (tree attr)
{
return emit_contract_conditions (attr, PRECONDITION_STMT);
}
/* Emit statements for postcondition attributes. */
static void
emit_postconditions_cleanup (tree contracts)
{
tree stmts = push_stmt_list ();
emit_contract_conditions (contracts, POSTCONDITION_STMT);
stmts = pop_stmt_list (stmts);
push_cleanup (NULL_TREE, stmts, /*eh_only*/false);
}
/* We're compiling the pre/postcondition function CONDFN; remap any FN
attributes that match CODE and emit them. */
static void
remap_and_emit_conditions (tree fn, tree condfn, tree_code code)
{
gcc_assert (code == PRECONDITION_STMT || code == POSTCONDITION_STMT);
for (tree attr = DECL_CONTRACTS (fn); attr;
attr = CONTRACT_CHAIN (attr))
{
tree contract = CONTRACT_STATEMENT (attr);
if (TREE_CODE (contract) == code)
{
contract = copy_node (contract);
remap_contract (fn, condfn, contract, /*duplicate_p=*/false);
emit_contract_statement (contract);
}
}
}
/* Converts a contract condition to bool and ensures it has a locaiton. */
tree
finish_contract_condition (cp_expr condition)
{
/* Ensure we have the condition location saved in case we later need to
emit a conversion error during template instantiation and wouldn't
otherwise have it. */
if (!CAN_HAVE_LOCATION_P (condition) || EXCEPTIONAL_CLASS_P (condition))
{
condition = build1_loc (condition.get_location (), VIEW_CONVERT_EXPR,
TREE_TYPE (condition), condition);
EXPR_LOCATION_WRAPPER_P (condition) = 1;
}
if (condition == error_mark_node || type_dependent_expression_p (condition))
return condition;
return condition_conversion (condition);
}
void
maybe_update_postconditions (tree fco)
{
/* Update any postconditions and the postcondition checking function
as needed. If there are postconditions, we'll use those to rewrite
return statements to check postconditions. */
if (has_active_postconditions (fco))
{
rebuild_postconditions (fco);
tree post = build_postcondition_function (fco);
set_postcondition_function (fco, post);
}
}
/* Called on attribute lists that must not contain contracts. If any
contracts are present, issue an error diagnostic and return true. */
bool
diagnose_misapplied_contracts (tree attributes)
{
if (attributes == NULL_TREE)
return false;
tree contract_attr = find_contract (attributes);
if (!contract_attr)
return false;
error_at (EXPR_LOCATION (CONTRACT_STATEMENT (contract_attr)),
"contracts must appertain to a function type");
/* Invalidate the contract so we don't treat it as valid later on. */
invalidate_contract (TREE_VALUE (TREE_VALUE (contract_attr)));
return true;
}
/* Build and return an argument list containing all the parameters of 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.
We use CALL_FROM_THUNK_P instead of forward_parm for forwarding
semantics. */
static vec *
build_arg_list (tree fn)
{
vec *args = make_tree_vector ();
for (tree t = DECL_ARGUMENTS (fn); t; t = DECL_CHAIN (t))
vec_safe_push (args, t);
return args;
}
void
start_function_contracts (tree decl1)
{
if (!handle_contracts_p (decl1))
return;
if (!outline_contracts_p (decl1))
{
emit_preconditions (DECL_CONTRACTS (current_function_decl));
emit_postconditions_cleanup (DECL_CONTRACTS (current_function_decl));
return;
}
/* 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))
build_contract_function_decls (decl1);
/* If we're starting a guarded function with valid contracts, we need to
insert a call to the pre function. */
if (DECL_PRE_FN (decl1)
&& DECL_PRE_FN (decl1) != error_mark_node)
{
releasing_vec args = build_arg_list (decl1);
tree call = build_call_a (DECL_PRE_FN (decl1),
args->length (),
args->address ());
CALL_FROM_THUNK_P (call) = true;
finish_expr_stmt (call);
}
}
/* Finish up the pre & post function definitions for a guarded FNDECL,
and compile those functions all the way to assembler language output. */
void
finish_function_contracts (tree fndecl)
{
if (!handle_contracts_p (fndecl)
|| !outline_contracts_p (fndecl))
return;
for (tree ca = DECL_CONTRACTS (fndecl); ca; ca = CONTRACT_CHAIN (ca))
{
tree contract = CONTRACT_STATEMENT (ca);
if (!CONTRACT_CONDITION (contract)
|| CONTRACT_CONDITION_DEFERRED_P (contract)
|| CONTRACT_CONDITION (contract) == error_mark_node)
return;
}
int flags = SF_DEFAULT | SF_PRE_PARSED;
/* If either the pre or post functions are bad, don't bother emitting
any contracts. The program is already ill-formed. */
tree pre = DECL_PRE_FN (fndecl);
tree post = DECL_POST_FN (fndecl);
if (pre == error_mark_node || post == error_mark_node)
return;
if (pre && DECL_INITIAL (fndecl) != error_mark_node)
{
DECL_PENDING_INLINE_P (pre) = false;
start_preparsed_function (pre, DECL_ATTRIBUTES (pre), flags);
remap_and_emit_conditions (fndecl, pre, PRECONDITION_STMT);
tree finished_pre = finish_function (false);
expand_or_defer_fn (finished_pre);
}
if (post && DECL_INITIAL (fndecl) != error_mark_node)
{
DECL_PENDING_INLINE_P (post) = false;
start_preparsed_function (post,
DECL_ATTRIBUTES (post),
flags);
remap_and_emit_conditions (fndecl, post, POSTCONDITION_STMT);
if (!VOID_TYPE_P (TREE_TYPE (TREE_TYPE (post))))
finish_return_stmt (get_postcondition_result_parameter (fndecl));
tree finished_post = finish_function (false);
expand_or_defer_fn (finished_post);
}
}
/* Rewrite the expression of a returned expression so that it invokes the
postcondition function as needed. */
tree
apply_postcondition_to_return (tree expr)
{
tree fn = current_function_decl;
tree post = DECL_POST_FN (fn);
if (!post)
return NULL_TREE;
/* If FN returns in memory, POST has a void return type and we call it when
EXPR is DECL_RESULT (fn). If FN returns a scalar, POST has the same
return type and we call it when EXPR is the value being returned. */
if (VOID_TYPE_P (TREE_TYPE (TREE_TYPE (post)))
!= (expr == DECL_RESULT (fn)))
return NULL_TREE;
releasing_vec args = build_arg_list (fn);
if (get_postcondition_result_parameter (fn))
vec_safe_push (args, expr);
tree call = build_call_a (post,
args->length (),
args->address ());
CALL_FROM_THUNK_P (call) = true;
return call;
}
/* A subroutine of duplicate_decls. Diagnose issues in the redeclaration of
guarded functions. */
void
duplicate_contracts (tree newdecl, tree olddecl)
{
if (TREE_CODE (newdecl) == TEMPLATE_DECL)
newdecl = DECL_TEMPLATE_RESULT (newdecl);
if (TREE_CODE (olddecl) == TEMPLATE_DECL)
olddecl = DECL_TEMPLATE_RESULT (olddecl);
/* Compare contracts to see if they match. */
tree old_contracts = DECL_CONTRACTS (olddecl);
tree new_contracts = DECL_CONTRACTS (newdecl);
if (!old_contracts && !new_contracts)
return;
location_t old_loc = DECL_SOURCE_LOCATION (olddecl);
location_t new_loc = DECL_SOURCE_LOCATION (newdecl);
/* If both declarations specify contracts, ensure they match.
TODO: This handles a potential error a little oddly. Consider:
struct B {
virtual void f(int n) [[pre: n == 0]];
};
struct D : B {
void f(int n) override; // inherits contracts
};
void D::f(int n) [[pre: n == 0]] // OK
{ }
It's okay because we're explicitly restating the inherited contract.
Changing the precondition on the definition D::f causes match_contracts
to complain about the mismatch.
This would previously have been diagnosed as adding contracts to an
override, but this seems like it should be well-formed. */
if (old_contracts && new_contracts)
{
if (!match_contract_conditions (old_loc, old_contracts,
new_loc, new_contracts,
cmc_declaration))
return;
if (DECL_UNIQUE_FRIEND_P (newdecl))
/* Newdecl's contracts are still DEFERRED_PARSE, and we're about to
collapse it into olddecl, so stash away olddecl's contracts for
later comparison. */
defer_guarded_contract_match (olddecl, olddecl, old_contracts);
}
/* Handle cases where contracts are omitted in one or the other
declaration. */
if (old_contracts)
{
/* Contracts have been previously specified by are no omitted. The
new declaration inherits the existing contracts. */
if (!new_contracts)
copy_contract_attributes (newdecl, olddecl);
/* In all cases, remove existing contracts from OLDDECL to prevent the
attribute merging function from adding excess contracts. */
remove_contract_attributes (olddecl);
}
else if (!old_contracts)
{
/* We are adding contracts to a declaration. */
if (new_contracts)
{
/* We can't add to a previously defined function. */
if (DECL_INITIAL (olddecl))
{
auto_diagnostic_group d;
error_at (new_loc, "cannot add contracts after definition");
inform (DECL_SOURCE_LOCATION (olddecl), "original definition here");
return;
}
/* We can't add to an unguarded virtual function declaration. */
if (DECL_VIRTUAL_P (olddecl) && new_contracts)
{
auto_diagnostic_group d;
error_at (new_loc, "cannot add contracts to a virtual function");
inform (DECL_SOURCE_LOCATION (olddecl), "original declaration here");
return;
}
/* Depending on the "first declaration" rule, we may not be able
to add contracts to a function after the fact. */
if (flag_contract_strict_declarations)
{
warning_at (new_loc,
OPT_fcontract_strict_declarations_,
"declaration adds contracts to %q#D",
olddecl);
return;
}
/* Copy the contracts from NEWDECL to OLDDECL. We shouldn't need to
remap them because NEWDECL's parameters will replace those of
OLDDECL. Remove the contracts from NEWDECL so they aren't
cloned when merging. */
copy_contract_attributes (olddecl, newdecl);
remove_contract_attributes (newdecl);
}
}
}
/* Replace the any contract attributes on OVERRIDER with a copy where any
references to BASEFN's PARM_DECLs have been rewritten to the corresponding
PARM_DECL in OVERRIDER. */
void
inherit_base_contracts (tree overrider, tree basefn)
{
tree last = NULL_TREE, contract_attrs = NULL_TREE;
for (tree a = DECL_CONTRACTS (basefn);
a != NULL_TREE;
a = CONTRACT_CHAIN (a))
{
tree c = copy_node (a);
TREE_VALUE (c) = build_tree_list (TREE_PURPOSE (TREE_VALUE (c)),
copy_node (CONTRACT_STATEMENT (c)));
tree src = basefn;
tree dst = overrider;
remap_contract (src, dst, CONTRACT_STATEMENT (c), /*duplicate_p=*/true);
CONTRACT_COMMENT (CONTRACT_STATEMENT (c)) =
copy_node (CONTRACT_COMMENT (CONTRACT_STATEMENT (c)));
chainon (last, c);
last = c;
if (!contract_attrs)
contract_attrs = c;
}
set_decl_contracts (overrider, contract_attrs);
}
#include "gt-cp-contracts.h"