Skip to content

Commit 2ffa79e

Browse files
author
Remi Delmas
committed
Function contracts: type checking rules for conditional targets in assigns clauses.
1 parent ffb732c commit 2ffa79e

File tree

2 files changed

+153
-20
lines changed

2 files changed

+153
-20
lines changed

src/ansi-c/c_typecheck_base.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,16 @@ class c_typecheck_baset:
146146
virtual void typecheck_start_thread(codet &code);
147147

148148
// contracts
149+
150+
/// \brief Typechecks a vector of assigns clause targets.
151+
///
152+
/// \remark The vector will grow larger in case it contains conditional
153+
/// targets of the form `c ? {t1, .. , tn}`, which get expanded to a
154+
/// collection `{c ? t1, .. , c ? tn }` by distributing the condition `c`
155+
/// on the list of targets.
149156
virtual void typecheck_spec_assigns(exprt::operandst &targets);
157+
virtual void typecheck_spec_assigns_condition(exprt &condition);
158+
virtual void typecheck_spec_assigns_target(exprt &target);
150159
virtual void typecheck_spec_loop_invariant(codet &code);
151160
virtual void typecheck_spec_decreases(codet &code);
152161

src/ansi-c/c_typecheck_code.cpp

Lines changed: 144 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Author: Daniel Kroening, [email protected]
1111

1212
#include "c_typecheck_base.h"
1313

14+
#include <util/arith_tools.h>
1415
#include <util/c_types.h>
1516
#include <util/config.h>
1617
#include <util/expr_util.h>
@@ -842,35 +843,158 @@ void c_typecheck_baset::typecheck_dowhile(code_dowhilet &code)
842843
}
843844
}
844845

846+
void c_typecheck_baset::typecheck_spec_assigns_condition(exprt &condition)
847+
{
848+
// compute type
849+
typecheck_expr(condition);
850+
851+
// non-fatal warnings
852+
if(has_subexpr(condition, [&](const exprt &subexpr) {
853+
return can_cast_expr<side_effect_expr_function_callt>(subexpr);
854+
}))
855+
{
856+
// Remark: we allow function calls without further checks for now because
857+
// they are mandatory for some applications.
858+
// The next step must be to check that the called functions have a contract
859+
// with an empty assigns clause and that they indeed satisfy their contract
860+
// using a CI check.
861+
warning().source_location = condition.source_location();
862+
warning()
863+
<< "function with possible side-effect called in target's condition"
864+
<< eom;
865+
}
866+
867+
// fatal errors
868+
bool must_throw = false;
869+
870+
if(condition.type().id() == ID_empty)
871+
{
872+
must_throw = true;
873+
error().source_location = condition.source_location();
874+
error() << "void-typed expressions not allowed as assigns clause conditions"
875+
<< eom;
876+
}
877+
878+
if(has_subexpr(condition, [&](const exprt &subexpr) {
879+
return can_cast_expr<side_effect_exprt>(subexpr) &&
880+
!can_cast_expr<side_effect_expr_function_callt>(subexpr);
881+
}))
882+
{
883+
must_throw = true;
884+
error().source_location = condition.source_location();
885+
error() << "side-effects not allowed in assigns clause conditions" << eom;
886+
}
887+
888+
if(has_subexpr(condition, ID_if))
889+
{
890+
must_throw = true;
891+
error().source_location = condition.source_location();
892+
error() << "ternary expressions not allowed in assigns "
893+
"clause conditions"
894+
<< eom;
895+
}
896+
897+
if(must_throw)
898+
throw 0;
899+
}
900+
901+
void c_typecheck_baset::typecheck_spec_assigns_target(exprt &target)
902+
{
903+
// compute type
904+
typecheck_expr(target);
905+
906+
// fatal errors
907+
bool must_throw = false;
908+
if(target.type().id() == ID_empty)
909+
{
910+
must_throw = true;
911+
error().source_location = target.source_location();
912+
error() << "void-typed expressions not allowed as assigns clause targets"
913+
<< eom;
914+
}
915+
916+
if(!target.get_bool(ID_C_lvalue) && target.id() != ID_pointer_object)
917+
{
918+
must_throw = true;
919+
error().source_location = target.source_location();
920+
error() << "assigns clause target must be an lvalue or "
921+
"__CPROVER_POINTER_OBJECT expression"
922+
<< eom;
923+
}
924+
925+
// Remark: an expression can be an lvalue and still contain side effects
926+
// or ternary expressions. We detect them in a second step.
927+
if(has_subexpr(target, ID_side_effect))
928+
{
929+
must_throw = true;
930+
error().source_location = target.source_location();
931+
error() << "side-effects not allowed in assigns clause targets" << eom;
932+
}
933+
934+
if(has_subexpr(target, ID_if))
935+
{
936+
must_throw = true;
937+
error().source_location = target.source_location();
938+
error() << "ternary expressions not allowed in assigns "
939+
"clause targets"
940+
<< eom;
941+
}
942+
943+
if(must_throw)
944+
throw 0;
945+
}
946+
845947
void c_typecheck_baset::typecheck_spec_assigns(exprt::operandst &targets)
846948
{
949+
exprt::operandst tmp;
847950
for(auto &target : targets)
848951
{
849-
typecheck_expr(target);
850-
851-
if(target.type().id() == ID_empty)
952+
// conditional target
953+
if(target.id() == ID_conditional_assigns_target)
852954
{
853-
error().source_location = target.source_location();
854-
error() << "void-typed targets not permitted in assigns clause" << eom;
855-
throw 0;
856-
}
857-
else if(target.id() == ID_pointer_object)
858-
{
859-
// skip
860-
}
861-
else if(!target.get_bool(ID_C_lvalue))
862-
{
863-
error().source_location = target.source_location();
864-
error() << "non-lvalue target in assigns clause" << eom;
865-
throw 0;
955+
// typecheck condition
956+
auto &condition = target.operands().at(0);
957+
typecheck_spec_assigns_condition(condition);
958+
959+
// make it boolean if needed
960+
if(!can_cast_type<bool_typet>(condition.type()))
961+
{
962+
if(can_cast_type<pointer_typet>(condition.type()))
963+
condition = notequal_exprt{
964+
condition, null_pointer_exprt(to_pointer_type(condition.type()))};
965+
else
966+
condition =
967+
notequal_exprt{condition, from_integer(0, condition.type())};
968+
}
969+
970+
// typecheck target(s)
971+
auto &conditional_target = target.operands().at(1);
972+
if(conditional_target.id() == ID_expression_list)
973+
{
974+
// distribute condition over all targets
975+
for(auto &each : conditional_target.operands())
976+
{
977+
typecheck_spec_assigns_target(each);
978+
exprt new_target{ID_conditional_assigns_target};
979+
new_target.add_to_operands(condition, std::move(each));
980+
tmp.push_back(std::move(new_target));
981+
}
982+
}
983+
else
984+
{
985+
typecheck_spec_assigns_target(conditional_target);
986+
tmp.push_back(std::move(target));
987+
}
866988
}
867-
else if(has_subexpr(target, ID_side_effect))
989+
else // unconditional target
868990
{
869-
error().source_location = target.source_location();
870-
error() << "assigns clause is not side-effect free" << eom;
871-
throw 0;
991+
typecheck_spec_assigns_target(target);
992+
tmp.push_back(std::move(target));
872993
}
873994
}
995+
targets.clear();
996+
for(auto &each : tmp)
997+
targets.push_back(std::move(each));
874998
}
875999

8761000
void c_typecheck_baset::typecheck_spec_loop_invariant(codet &code)

0 commit comments

Comments
 (0)