Skip to content

Commit 0bb7a04

Browse files
committed
Introduce enum_is_in_range expression
We previously maintained a function call, which would end up being type in-consistent if there were multiple uses of `__CPROVER_enum_is_in_range` with distinct enum types. Instead, have the C front-end create an (and immediately lower) an expression. The only bit of care that's currently required is to ensure that goto_check_ct does not end up creating assertions over the resulting expression.
1 parent eaae3b6 commit 0bb7a04

File tree

10 files changed

+112
-93
lines changed

10 files changed

+112
-93
lines changed

regression/cbmc/enum_is_in_range/enum_test3-simplified.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
CORE new-smt-backend
22
enum_test3.c
33
--show-goto-functions
4-
0 ≤ cast\(main::1::ev1, (un)?signedbv\[\d+\]\) ∧ cast\(main::1::ev1, (un)?signedbv\[\d+\]\) ≤ 4$
4+
0 ≤ cast\(main::1::ev1, (un)?signedbv\[\d+\]\) ∧ cast\(main::1::ev1, (un)?signedbv\[\d+\]\) ≤ 4
55
^EXIT=0$
66
^SIGNAL=0$
77
--

src/ansi-c/c_expr.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ Author: Daniel Kroening, [email protected]
99
#include "c_expr.h"
1010

1111
#include <util/arith_tools.h>
12+
#include <util/c_types.h>
13+
#include <util/namespace.h>
14+
#include <util/simplify_expr.h>
1215

1316
shuffle_vector_exprt::shuffle_vector_exprt(
1417
exprt vector1,
@@ -64,3 +67,19 @@ vector_exprt shuffle_vector_exprt::lower() const
6467

6568
return vector_exprt{std::move(operands), type()};
6669
}
70+
71+
exprt enum_is_in_range_exprt::lower(const namespacet &ns) const
72+
{
73+
const auto &enum_type = to_c_enum_tag_type(op().type());
74+
const c_enum_typet &c_enum_type = ns.follow_tag(enum_type);
75+
const c_enum_typet::memberst enum_values = c_enum_type.members();
76+
77+
exprt::operandst disjuncts;
78+
for(const auto &enum_value : enum_values)
79+
{
80+
constant_exprt val{enum_value.get_value(), enum_type};
81+
disjuncts.push_back(equal_exprt(op(), std::move(val)));
82+
}
83+
84+
return simplify_expr(disjunction(disjuncts), ns);
85+
}

src/ansi-c/c_expr.h

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,4 +320,54 @@ to_conditional_target_group_expr(exprt &expr)
320320
return ret;
321321
}
322322

323+
/// \brief A Boolean expression returning true, iff the value of an enum-typed
324+
/// symbol equals one of the enum's declared values.
325+
class enum_is_in_range_exprt : public unary_predicate_exprt
326+
{
327+
public:
328+
explicit enum_is_in_range_exprt(exprt _op)
329+
: unary_predicate_exprt(ID_enum_is_in_range, std::move(_op))
330+
{
331+
op().add_source_location().add_pragma("disable:enum-range-check");
332+
}
333+
334+
exprt lower(const namespacet &ns) const;
335+
};
336+
337+
template <>
338+
inline bool can_cast_expr<enum_is_in_range_exprt>(const exprt &base)
339+
{
340+
return base.id() == ID_enum_is_in_range;
341+
}
342+
343+
inline void validate_expr(const enum_is_in_range_exprt &expr)
344+
{
345+
validate_operands(
346+
expr, 1, "enum_is_in_range expression must have one operand");
347+
}
348+
349+
/// \brief Cast an exprt to an \ref enum_is_in_range_exprt
350+
///
351+
/// \a expr must be known to be \ref enum_is_in_range_exprt.
352+
///
353+
/// \param expr: Source expression
354+
/// \return Object of type \ref enum_is_in_range_exprt
355+
inline const enum_is_in_range_exprt &to_enum_is_in_range_expr(const exprt &expr)
356+
{
357+
PRECONDITION(expr.id() == ID_enum_is_in_range);
358+
const enum_is_in_range_exprt &ret =
359+
static_cast<const enum_is_in_range_exprt &>(expr);
360+
validate_expr(ret);
361+
return ret;
362+
}
363+
364+
/// \copydoc to_side_effect_expr_overflow(const exprt &)
365+
inline enum_is_in_range_exprt &to_enum_is_in_range_expr(exprt &expr)
366+
{
367+
PRECONDITION(expr.id() == ID_enum_is_in_range);
368+
enum_is_in_range_exprt &ret = static_cast<enum_is_in_range_exprt &>(expr);
369+
validate_expr(ret);
370+
return ret;
371+
}
372+
323373
#endif // CPROVER_ANSI_C_C_EXPR_H

src/ansi-c/c_typecheck_expr.cpp

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2118,6 +2118,35 @@ void c_typecheck_baset::typecheck_side_effect_function_call(
21182118

21192119
return;
21202120
}
2121+
else if(identifier == CPROVER_PREFIX "enum_is_in_range")
2122+
{
2123+
// Check correct number of arguments
2124+
if(expr.arguments().size() != 1)
2125+
{
2126+
std::ostringstream error_message;
2127+
error_message << identifier << " takes exactly 1 argument, but "
2128+
<< expr.arguments().size() << " were provided";
2129+
throw invalid_source_file_exceptiont{
2130+
error_message.str(), expr.source_location()};
2131+
}
2132+
const auto &arg1 = expr.arguments()[0];
2133+
if(!can_cast_type<c_enum_tag_typet>(arg1.type()))
2134+
{
2135+
// Can't enum range check a non-enum
2136+
std::ostringstream error_message;
2137+
error_message << identifier << " expects enum, but ("
2138+
<< expr2c(arg1, *this) << ") has type `"
2139+
<< type2c(arg1.type(), *this) << '`';
2140+
throw invalid_source_file_exceptiont{
2141+
error_message.str(), expr.source_location()};
2142+
}
2143+
2144+
enum_is_in_range_exprt in_range{arg1};
2145+
in_range.add_source_location() = expr.source_location();
2146+
exprt lowered = in_range.lower(*this);
2147+
expr.swap(lowered);
2148+
return;
2149+
}
21212150
else if(
21222151
auto gcc_polymorphic = typecheck_gcc_polymorphic_builtin(
21232152
identifier, expr.arguments(), f_op.source_location()))
@@ -3579,34 +3608,6 @@ exprt c_typecheck_baset::do_special_functions(
35793608
overflow.add_source_location() = tmp.source_location();
35803609
return std::move(overflow);
35813610
}
3582-
else if(identifier == CPROVER_PREFIX "enum_is_in_range")
3583-
{
3584-
// Check correct number of arguments
3585-
if(expr.arguments().size() != 1)
3586-
{
3587-
std::ostringstream error_message;
3588-
error_message << identifier << " takes exactly 1 argument, but "
3589-
<< expr.arguments().size() << " were provided";
3590-
throw invalid_source_file_exceptiont{
3591-
error_message.str(), expr.source_location()};
3592-
}
3593-
auto arg1 = expr.arguments()[0];
3594-
typecheck_expr(arg1);
3595-
if(!can_cast_type<c_enum_tag_typet>(arg1.type()))
3596-
{
3597-
// Can't enum range check a non-enum
3598-
std::ostringstream error_message;
3599-
error_message << identifier << " expects enum, but ("
3600-
<< expr2c(arg1, *this) << ") has type `"
3601-
<< type2c(arg1.type(), *this) << '`';
3602-
throw invalid_source_file_exceptiont{
3603-
error_message.str(), expr.source_location()};
3604-
}
3605-
else
3606-
{
3607-
return expr;
3608-
}
3609-
}
36103611
else if(
36113612
identifier == "__builtin_add_overflow" ||
36123613
identifier == "__builtin_sadd_overflow" ||

src/ansi-c/cprover_builtin_headers.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,6 @@ __CPROVER_bool __CPROVER_overflow_plus();
137137
__CPROVER_bool __CPROVER_overflow_shl();
138138
__CPROVER_bool __CPROVER_overflow_unary_minus();
139139

140-
// enumerations
141-
__CPROVER_bool __CPROVER_enum_is_in_range();
142-
143140
// contracts
144141
void __CPROVER_assignable(void *ptr, __CPROVER_size_t size,
145142
__CPROVER_bool is_ptr_to_ptr);

src/ansi-c/goto_check_c.cpp

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ Author: Daniel Kroening, [email protected]
4141
#include <langapi/language.h>
4242
#include <langapi/mode.h>
4343

44+
#include "c_expr.h"
45+
4446
#include <algorithm>
4547

4648
class goto_check_ct
@@ -523,19 +525,15 @@ void goto_check_ct::enum_range_check(const exprt &expr, const guardt &guard)
523525
if(!enable_enum_range_check)
524526
return;
525527

526-
const c_enum_tag_typet &c_enum_tag_type = to_c_enum_tag_type(expr.type());
527-
const c_enum_typet &c_enum_type = ns.follow_tag(c_enum_tag_type);
528-
529-
const c_enum_typet::memberst enum_values = c_enum_type.members();
530-
531-
std::vector<exprt> disjuncts;
532-
for(const auto &enum_value : enum_values)
528+
// we might be looking at a lowered enum_is_in_range_exprt, skip over these
529+
const auto &pragmas = expr.source_location().get_pragmas();
530+
for(const auto &d : pragmas)
533531
{
534-
const constant_exprt val{enum_value.get_value(), c_enum_tag_type};
535-
disjuncts.push_back(equal_exprt(expr, val));
532+
if(d.first == "disable:enum-range-check")
533+
return;
536534
}
537535

538-
const exprt check = disjunction(disjuncts);
536+
const exprt check = enum_is_in_range_exprt{expr}.lower(ns);
539537

540538
add_guarded_property(
541539
check,

src/ansi-c/library/cprover.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ struct __CPROVER_pipet {
4040
short next_unread;
4141
};
4242

43+
// enumerations
44+
// expects one enum-typed argument
45+
__CPROVER_bool __CPROVER_enum_is_in_range(unsigned long long);
46+
4347
#include "../cprover_builtin_headers.h"
4448

4549
#endif // CPROVER_ANSI_C_LIBRARY_CPROVER_H

src/goto-programs/builtin_functions.cpp

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -615,33 +615,6 @@ exprt make_va_list(const exprt &expr)
615615
return result;
616616
}
617617

618-
void goto_convertt::do_enum_is_in_range(
619-
const exprt &lhs,
620-
const symbol_exprt &function,
621-
const exprt::operandst &arguments,
622-
goto_programt &dest)
623-
{
624-
PRECONDITION(arguments.size() == 1);
625-
const auto enum_expr = arguments.front();
626-
const auto enum_type =
627-
type_try_dynamic_cast<c_enum_tag_typet>(enum_expr.type());
628-
PRECONDITION(enum_type);
629-
const c_enum_typet &c_enum_type = ns.follow_tag(*enum_type);
630-
const c_enum_typet::memberst enum_values = c_enum_type.members();
631-
632-
exprt::operandst disjuncts;
633-
for(const auto &enum_value : enum_values)
634-
{
635-
constant_exprt val{enum_value.get_value(), *enum_type};
636-
disjuncts.push_back(equal_exprt(enum_expr, std::move(val)));
637-
}
638-
639-
code_assignt assignment(lhs, simplify_expr(disjunction(disjuncts), ns));
640-
assignment.add_source_location() = function.source_location();
641-
assignment.add_source_location().add_pragma("disable:enum-range-check");
642-
copy(assignment, ASSIGN, dest);
643-
}
644-
645618
void goto_convertt::do_havoc_slice(
646619
const exprt &lhs,
647620
const symbol_exprt &function,
@@ -956,10 +929,6 @@ void goto_convertt::do_function_call_symbol(
956929
throw 0;
957930
}
958931
}
959-
else if(identifier == CPROVER_PREFIX "enum_is_in_range")
960-
{
961-
do_enum_is_in_range(lhs, function, arguments, dest);
962-
}
963932
else if(
964933
identifier == CPROVER_PREFIX "assert" ||
965934
identifier == CPROVER_PREFIX "precondition" ||

src/goto-programs/goto_convert_class.h

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -610,26 +610,6 @@ class goto_convertt:public messaget
610610
bool get_string_constant(const exprt &expr, irep_idt &);
611611
exprt get_constant(const exprt &expr);
612612

613-
/// \brief Converts calls to the built in `enum_is_in_range` function to a
614-
/// test that the given enum value is in the valid range of values for that
615-
/// enum type.
616-
///
617-
/// Note that the check for the range of values is done by creating a
618-
/// disjunction comparing the expression with each possible valid value.
619-
/// \param lhs: The destination for the generated assignment.
620-
/// \param function: The `enum_is_in_range` symbol of the source function call.
621-
/// \param arguments: A collection of arguments, which is expected to contain a
622-
/// single argument which is an expression that resolves to a value of
623-
/// enum type. The arguments are expected to have been prevalidated by the
624-
/// typechecking process.
625-
/// \param dest: The goto program into which the generated assignment is
626-
/// copied.
627-
void do_enum_is_in_range(
628-
const exprt &lhs,
629-
const symbol_exprt &function,
630-
const exprt::operandst &arguments,
631-
goto_programt &dest);
632-
633613
// some built-in functions
634614
void do_atomic_begin(
635615
const exprt &lhs,

src/util/irep_ids.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,7 @@ IREP_ID_TWO(overflow_result_shl, overflow_result-shl)
903903
IREP_ID_TWO(overflow_result_unary_minus, overflow_result-unary-)
904904
IREP_ID_ONE(field_sensitive_ssa)
905905
IREP_ID_ONE(checked_assigns)
906+
IREP_ID_ONE(enum_is_in_range)
906907

907908
// Projects depending on this code base that wish to extend the list of
908909
// available ids should provide a file local_irep_ids.def in their source tree

0 commit comments

Comments
 (0)