Skip to content

Commit 1e3ec73

Browse files
committed
Adds support for pointer history variables in function contracts
This adds support for history variables in function contracts. History variables are (1) declared and (2) assigned to the value that their corresponding variable has at function call time. Currently, only pointers are supported.
1 parent 8759d46 commit 1e3ec73

File tree

7 files changed

+230
-9
lines changed

7 files changed

+230
-9
lines changed

src/ansi-c/c_typecheck_expr.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2575,6 +2575,20 @@ exprt c_typecheck_baset::do_special_functions(
25752575

25762576
return std::move(ok_expr);
25772577
}
2578+
else if(identifier == CPROVER_PREFIX "old")
2579+
{
2580+
if(expr.arguments().size() != 1)
2581+
{
2582+
error().source_location = f_op.source_location();
2583+
error() << identifier << " expects one operands" << eom;
2584+
throw 0;
2585+
}
2586+
2587+
old_exprt old_expr(ID_old, expr.arguments()[0]);
2588+
old_expr.add_source_location() = source_location;
2589+
2590+
return std::move(old_expr);
2591+
}
25782592
else if(identifier==CPROVER_PREFIX "isinff" ||
25792593
identifier==CPROVER_PREFIX "isinfd" ||
25802594
identifier==CPROVER_PREFIX "isinfld" ||

src/ansi-c/cprover_builtin_headers.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ __CPROVER_size_t __CPROVER_zero_string_length(const void *);
1212
__CPROVER_size_t __CPROVER_buffer_size(const void *);
1313
__CPROVER_bool __CPROVER_r_ok(const void *, __CPROVER_size_t);
1414
__CPROVER_bool __CPROVER_w_ok(const void *, __CPROVER_size_t);
15+
void __CPROVER_old(const void *);
1516

1617
// bitvector analysis
1718
__CPROVER_bool __CPROVER_get_flag(const void *, const char *);

src/ansi-c/library/cprover.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ __CPROVER_size_t __CPROVER_zero_string_length(const void *);
3535
__CPROVER_size_t __CPROVER_buffer_size(const void *);
3636
__CPROVER_bool __CPROVER_r_ok(const void *, __CPROVER_size_t);
3737
__CPROVER_bool __CPROVER_w_ok(const void *, __CPROVER_size_t);
38+
void __CPROVER_old(const void *);
3839

3940
#if 0
4041
__CPROVER_bool __CPROVER_equal();

src/goto-instrument/code_contracts.cpp

Lines changed: 167 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ Date: February 2016
2121

2222
#include <linking/static_lifetime_init.h>
2323

24+
#include <fstream>
25+
#include <iostream>
26+
#include <map>
2427
#include <util/arith_tools.h>
2528
#include <util/c_types.h>
2629
#include <util/expr_util.h>
@@ -234,6 +237,121 @@ void code_contractst::add_quantified_variable(
234237
}
235238
}
236239

240+
optionalt<exprt> code_contractst::replace_old_parameter(
241+
exprt expr,
242+
std::map<exprt, exprt> &parameter2ghost,
243+
source_locationt location,
244+
const irep_idt &function,
245+
const irep_idt &mode,
246+
std::list<goto_programt::instructiont> &instructions)
247+
{
248+
bool modified = false;
249+
250+
for(auto &op : expr.operands())
251+
{
252+
auto op_result = replace_old_parameter(
253+
op, parameter2ghost, location, function, mode, instructions);
254+
if(op_result.has_value())
255+
{
256+
op = *op_result;
257+
modified = true;
258+
}
259+
}
260+
261+
if(expr.id() == ID_old)
262+
{
263+
DATA_INVARIANT(expr.operands().size() == 1, "old must have one operands");
264+
265+
const auto &parameter = to_old_expr(expr).variable();
266+
267+
// TODO: generalize below
268+
PRECONDITION(parameter.id() == ID_dereference);
269+
270+
exprt ret;
271+
if(parameter.id() == ID_dereference)
272+
{
273+
const auto &dereference_expr = to_dereference_expr(parameter);
274+
const auto &cur_pointer = dereference_expr.pointer();
275+
276+
std::map<exprt, exprt>::iterator it = parameter2ghost.find(cur_pointer);
277+
278+
if(it == parameter2ghost.end())
279+
{
280+
// 1. Create a temporary symbol expression that represents the
281+
// history variable
282+
symbol_exprt tmp_symbol =
283+
new_tmp_symbol(cur_pointer.type(), location, function, mode)
284+
.symbol_expr();
285+
286+
// 2. Associate the above temporary variable to it's corresponding
287+
// expression
288+
parameter2ghost[cur_pointer] = tmp_symbol;
289+
290+
// 3. Add the required instructions to the instructions list
291+
// 3.1 Declare the newly created temporary variable
292+
instructions.push_back(goto_programt::make_decl(tmp_symbol, location));
293+
294+
// 3.2 Add an assignment such that the value pointed to by the new
295+
// temporary variable is equal to the value of the corresponding parameter
296+
instructions.push_back(goto_programt::make_assignment(
297+
dereference_exprt(tmp_symbol),
298+
dereference_exprt(cur_pointer),
299+
location));
300+
}
301+
302+
ret = dereference_exprt(parameter2ghost[cur_pointer]);
303+
}
304+
305+
return ret;
306+
}
307+
else if(modified)
308+
return std::move(expr);
309+
else
310+
return {};
311+
}
312+
313+
std::pair<goto_programt::instructiont, std::list<goto_programt::instructiont>>
314+
code_contractst::create_ensures_instruction(
315+
exprt expression,
316+
source_locationt location,
317+
const irep_idt &function,
318+
const irep_idt &mode,
319+
bool isEnforces)
320+
{
321+
std::map<exprt, exprt> parameter2ghost;
322+
std::list<goto_programt::instructiont> instructions;
323+
324+
// Create instruction corresponding to the ensures clause
325+
goto_programt::instructiont instruction;
326+
if(isEnforces)
327+
instruction = goto_programt::make_assertion(expression, location);
328+
else
329+
instruction = goto_programt::make_assumption(expression, location);
330+
331+
// Find and replace "old" expression in the above instruction
332+
if(has_subexpr(instruction.get_condition(), [](const exprt &expr) {
333+
return expr.id() == ID_old;
334+
}))
335+
{
336+
auto old_cond = replace_old_parameter(
337+
instruction.get_condition(),
338+
parameter2ghost,
339+
location,
340+
function,
341+
mode,
342+
instructions);
343+
if(old_cond.has_value())
344+
{
345+
instruction.set_condition(*old_cond);
346+
}
347+
}
348+
349+
// return a pair containing:
350+
// 1. the instruction corresponding to the ensures clause
351+
// 2. instructions related to initializing the history variables
352+
return std::make_pair(instruction, instructions);
353+
}
354+
237355
bool code_contractst::apply_function_contract(
238356
const irep_idt &function_id,
239357
goto_programt &goto_program,
@@ -301,7 +419,7 @@ bool code_contractst::apply_function_contract(
301419
}
302420

303421
// Replace formal parameters
304-
code_function_callt::argumentst::const_iterator a_it=
422+
code_function_callt::argumentst::const_iterator a_it =
305423
call.arguments().begin();
306424
for(code_typet::parameterst::const_iterator p_it = type.parameters().begin();
307425
p_it != type.parameters().end() && a_it != call.arguments().end();
@@ -353,7 +471,25 @@ bool code_contractst::apply_function_contract(
353471
// function call with a SKIP statement.
354472
if(ensures.is_not_nil())
355473
{
356-
*target = goto_programt::make_assumption(ensures, target->source_location);
474+
// get all the relevant instructions related to history variables
475+
std::
476+
pair<goto_programt::instructiont, std::list<goto_programt::instructiont>>
477+
ensures_pair = create_ensures_instruction(
478+
ensures,
479+
ensures.source_location(),
480+
function,
481+
function_symbol.mode,
482+
false);
483+
484+
// add all the history variable initializations
485+
std::list<goto_programt::instructiont>::iterator it;
486+
for(it = ensures_pair.second.begin(); it != ensures_pair.second.end(); ++it)
487+
{
488+
goto_program.insert_before(target, std::move(*it));
489+
}
490+
491+
// add the ensures instructions
492+
*target = std::move(ensures_pair.first);
357493
}
358494
else
359495
{
@@ -793,6 +929,7 @@ void code_contractst::add_contract_check(
793929
// if(nondet)
794930
// decl ret
795931
// decl parameter1 ...
932+
// decl ghost_parameter1 ... [optional]
796933
// assume(requires) [optional]
797934
// ret=function(parameter1, ...)
798935
// assert(ensures)
@@ -821,7 +958,7 @@ void code_contractst::add_contract_check(
821958
.symbol_expr();
822959
check.add(goto_programt::make_decl(r, skip->source_location));
823960

824-
call.lhs()=r;
961+
call.lhs() = r;
825962
return_stmt = code_returnt(r);
826963

827964
symbol_exprt ret_val(CPROVER_PREFIX "return_value", call.lhs().type());
@@ -860,6 +997,32 @@ void code_contractst::add_contract_check(
860997
code_contractst::add_quantified_variable(
861998
requires, replace, function_symbol.mode);
862999

1000+
// rewrite any use of __CPROVER_return_value
1001+
exprt ensures_cond = ensures;
1002+
replace(ensures_cond);
1003+
1004+
// Prepare the history variables handling
1005+
std::pair<goto_programt::instructiont, std::list<goto_programt::instructiont>>
1006+
ensures_pair;
1007+
1008+
if(ensures.is_not_nil())
1009+
{
1010+
// get all the relevant instructions related to history variables
1011+
ensures_pair = create_ensures_instruction(
1012+
ensures_cond,
1013+
ensures.source_location(),
1014+
wrapper_fun,
1015+
function_symbol.mode,
1016+
true);
1017+
1018+
// add all the history variable initializations
1019+
std::list<goto_programt::instructiont>::iterator it;
1020+
for(it = ensures_pair.second.begin(); it != ensures_pair.second.end(); ++it)
1021+
{
1022+
check.add(std::move(*it));
1023+
}
1024+
}
1025+
8631026
// assume(requires)
8641027
if(requires.is_not_nil())
8651028
{
@@ -874,15 +1037,10 @@ void code_contractst::add_contract_check(
8741037
// ret=mangled_fun(parameter1, ...)
8751038
check.add(goto_programt::make_function_call(call, skip->source_location));
8761039

877-
// rewrite any use of __CPROVER_return_value
878-
exprt ensures_cond = ensures;
879-
replace(ensures_cond);
880-
8811040
// assert(ensures)
8821041
if(ensures.is_not_nil())
8831042
{
884-
check.add(
885-
goto_programt::make_assertion(ensures_cond, ensures.source_location()));
1043+
check.add(std::move(ensures_pair.first));
8861044
}
8871045

8881046
if(code_type.return_type() != empty_typet())

src/goto-instrument/code_contracts.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Date: February 2016
1414
#ifndef CPROVER_GOTO_INSTRUMENT_CODE_CONTRACTS_H
1515
#define CPROVER_GOTO_INSTRUMENT_CODE_CONTRACTS_H
1616

17+
#include <map>
1718
#include <set>
1819
#include <string>
1920
#include <unordered_set>
@@ -175,6 +176,27 @@ class code_contractst
175176
exprt expression,
176177
replace_symbolt &replace,
177178
irep_idt mode);
179+
180+
/// This function recursively identifies the "old" expressions within expr
181+
/// and replaces them with correspoding history variables.
182+
optionalt<exprt> replace_old_parameter(
183+
exprt expr,
184+
std::map<exprt, exprt> &parameter2ghost,
185+
source_locationt location,
186+
const irep_idt &function,
187+
const irep_idt &mode,
188+
std::list<goto_programt::instructiont> &instructions);
189+
190+
/// This function creates and returns an instruction that corresponds to the
191+
/// ensures clause. It also returns a list of instructions related to
192+
/// initializing history variables, if required.
193+
std::pair<goto_programt::instructiont, std::list<goto_programt::instructiont>>
194+
create_ensures_instruction(
195+
exprt expression,
196+
source_locationt location,
197+
const irep_idt &function,
198+
const irep_idt &mode,
199+
bool isEnforces);
178200
};
179201

180202
#define FLAG_REPLACE_CALL "replace-call-with-contract"

src/util/irep_ids.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,7 @@ IREP_ID_TWO(C_qualifier, #qualifier)
684684
IREP_ID_TWO(C_array_ini, #array_ini)
685685
IREP_ID_ONE(r_ok)
686686
IREP_ID_ONE(w_ok)
687+
IREP_ID_ONE(old)
687688
IREP_ID_ONE(super_class)
688689
IREP_ID_ONE(exceptions_thrown_list)
689690
IREP_ID_TWO(C_java_method_type, #java_method_type)

src/util/pointer_expr.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,30 @@ class null_pointer_exprt : public constant_exprt
462462
}
463463
};
464464

465+
/// \brief A base class for a predicate that indicates the pre-function-call
466+
/// value of a variable passed as a parameter to a function
467+
class old_exprt : public unary_predicate_exprt
468+
{
469+
public:
470+
explicit old_exprt(irep_idt id, exprt variable)
471+
: unary_predicate_exprt(id, std::move(variable))
472+
{
473+
}
474+
475+
const exprt &variable() const
476+
{
477+
return op0();
478+
}
479+
};
480+
481+
inline const old_exprt &to_old_expr(const exprt &expr)
482+
{
483+
PRECONDITION(expr.id() == ID_old);
484+
auto &ret = static_cast<const old_exprt &>(expr);
485+
validate_expr(ret);
486+
return ret;
487+
}
488+
465489
/// \brief A base class for a predicate that indicates that an
466490
/// address range is ok to read or write
467491
class r_or_w_ok_exprt : public binary_predicate_exprt

0 commit comments

Comments
 (0)