Skip to content

Commit be1cbf2

Browse files
tautschnigDaniel Kroening
authored and
Daniel Kroening
committed
Move printf to C library and support return values
Code using var = printf(...); (printf does return a value!) previously resulted in a failing invariant as the symex_assign code assumed the lhs always was nil. Since symex does not know how to generate a proper return value, make __CPROVER_printf a void-typed built-in and model printf around it. Clean up the corresponding goto-convert code.
1 parent 2a45e76 commit be1cbf2

File tree

10 files changed

+133
-54
lines changed

10 files changed

+133
-54
lines changed

doc/cprover-manual/api.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@ using nondeterminism, as described [here](../modeling/nondeterminism/)). The
6262
string constant is followed by an arbitrary number of values of
6363
arbitrary types.
6464

65+
#### \_\_CPROVER\_printf
66+
67+
```C
68+
void __CPROVER_printf(const char *format, ...);
69+
```
70+
71+
The function **\_\_CPROVER\_printf** implements the C `printf` function (without
72+
any return value). The observable effect is that its output is shown within a
73+
counterexample trace.
74+
6575
#### \_\_CPROVER\_cover
6676
6777
```C
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#include <assert.h>
2+
#include <stdio.h>
3+
4+
int main()
5+
{
6+
int x;
7+
x = printf("x");
8+
assert(x < 0);
9+
return 0;
10+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CORE
2+
main.c
3+
--pointer-check --bounds-check
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
^\*\* 1 of \d+ failed
7+
^VERIFICATION FAILED$
8+
--
9+
^warning: ignoring

regression/cbmc/printf1/test.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
CORE
1+
CORE broken-smt-backend
22
main.c
33
--trace
44
^EXIT=10$

src/ansi-c/cprover_builtin_headers.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ void __CPROVER_cleanup(const void *, const void *);
2222
__CPROVER_bool __CPROVER_get_must(const void *, const char *);
2323
__CPROVER_bool __CPROVER_get_may(const void *, const char *);
2424

25+
void __CPROVER_printf(const char *format, ...);
2526
void __CPROVER_input(const char *id, ...);
2627
void __CPROVER_output(const char *id, ...);
2728
void __CPROVER_cover(__CPROVER_bool condition);

src/ansi-c/library/cprover.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ void __CPROVER_initialize(void);
3838
void __CPROVER_cover(__CPROVER_bool condition);
3939
#endif
4040

41+
// NOLINTNEXTLINE(build/deprecated)
42+
void __CPROVER_printf(const char *format, ...);
4143
void __CPROVER_input(const char *id, ...);
4244
void __CPROVER_output(const char *id, ...);
4345

src/ansi-c/library/stdio.c

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ inline int putchar(int c)
2222
{
2323
__CPROVER_HIDE:;
2424
__CPROVER_bool error=__VERIFIER_nondet___CPROVER_bool();
25-
printf("%c", c);
25+
__CPROVER_printf("%c", c);
2626
return (error?-1:c);
2727
}
2828

@@ -41,7 +41,7 @@ inline int puts(const char *s)
4141
__CPROVER_HIDE:;
4242
__CPROVER_bool error=__VERIFIER_nondet___CPROVER_bool();
4343
int ret=__VERIFIER_nondet_int();
44-
printf("%s\n", s);
44+
__CPROVER_printf("%s\n", s);
4545
if(error) ret=-1; else __CPROVER_assume(ret>=0);
4646
return ret;
4747
}
@@ -768,7 +768,7 @@ void perror(const char *s)
768768
#endif
769769
// should go to stderr
770770
if(s[0]!=0)
771-
printf("%s: ", s);
771+
__CPROVER_printf("%s: ", s);
772772
}
773773

774774
// TODO: print errno error
@@ -921,6 +921,31 @@ inline int vsscanf(const char *restrict s, const char *restrict format, va_list
921921
return result;
922922
}
923923

924+
/* FUNCTION: printf */
925+
926+
#ifndef __CPROVER_STDIO_H_INCLUDED
927+
# include <stdio.h>
928+
# define __CPROVER_STDIO_H_INCLUDED
929+
#endif
930+
931+
#ifndef __CPROVER_STDARG_H_INCLUDED
932+
# include <stdarg.h>
933+
# define __CPROVER_STDARG_H_INCLUDED
934+
#endif
935+
936+
int __VERIFIER_nondet_int();
937+
938+
inline int printf(const char *format, ...)
939+
{
940+
__CPROVER_HIDE:;
941+
int result = __VERIFIER_nondet_int();
942+
va_list list;
943+
va_start(list, format);
944+
__CPROVER_printf(format, list);
945+
va_end(list);
946+
return result;
947+
}
948+
924949
/* FUNCTION: fprintf */
925950

926951
#ifndef __CPROVER_STDIO_H_INCLUDED

src/goto-programs/builtin_functions.cpp

Lines changed: 3 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -203,33 +203,10 @@ void goto_convertt::do_printf(
203203
{
204204
const irep_idt &f_id = function.get_identifier();
205205

206-
if(f_id==CPROVER_PREFIX "printf" ||
207-
f_id=="printf")
208-
{
209-
const typet &return_type = to_code_type(function.type()).return_type();
210-
side_effect_exprt printf_code(
211-
ID_printf, return_type, function.source_location());
212-
213-
printf_code.operands()=arguments;
214-
printf_code.add_source_location()=function.source_location();
206+
PRECONDITION(f_id == CPROVER_PREFIX "printf");
215207

216-
if(lhs.is_not_nil())
217-
{
218-
code_assignt assignment(lhs, printf_code);
219-
assignment.add_source_location()=function.source_location();
220-
copy(assignment, ASSIGN, dest);
221-
}
222-
else
223-
{
224-
printf_code.id(ID_code);
225-
printf_code.type() = code_typet(
226-
code_typet::parameterst(to_code_type(function.type()).parameters()),
227-
empty_typet());
228-
copy(to_code(printf_code), OTHER, dest);
229-
}
230-
}
231-
else
232-
UNREACHABLE;
208+
codet printf_code(ID_printf, arguments, function.source_location());
209+
copy(printf_code, OTHER, dest);
233210
}
234211

235212
void goto_convertt::do_scanf(
@@ -927,15 +904,6 @@ void goto_convertt::do_function_call_symbol(
927904
{
928905
do_array_op(ID_array_replace, lhs, function, arguments, dest);
929906
}
930-
else if(identifier=="printf")
931-
/*
932-
identifier=="fprintf" ||
933-
identifier=="sprintf" ||
934-
identifier=="snprintf")
935-
*/
936-
{
937-
do_printf(lhs, function, arguments, dest);
938-
}
939907
else if(identifier=="__assert_fail" ||
940908
identifier=="_assert" ||
941909
identifier=="__assert_c99" ||

src/goto-symex/symex_assign.cpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,6 @@ void goto_symext::symex_assign(statet &state, const code_assignt &code)
4545
symex_cpp_new(state, lhs, side_effect_expr);
4646
else if(statement==ID_allocate)
4747
symex_allocate(state, lhs, side_effect_expr);
48-
else if(statement==ID_printf)
49-
{
50-
PRECONDITION(lhs.is_nil());
51-
symex_printf(state, side_effect_expr);
52-
}
5348
else if(statement == ID_va_start)
5449
symex_va_start(state, lhs, side_effect_expr);
5550
else

src/goto-symex/symex_builtin_functions.cpp

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -303,19 +303,19 @@ void goto_symext::symex_va_start(
303303
code_assignt{lhs, typecast_exprt::conditional_cast(rhs, lhs.type())});
304304
}
305305

306-
irep_idt get_string_argument_rec(const exprt &src)
306+
static irep_idt get_string_argument_rec(const exprt &src)
307307
{
308308
if(src.id()==ID_typecast)
309309
{
310-
PRECONDITION(src.operands().size() == 1);
311-
return get_string_argument_rec(src.op0());
310+
return get_string_argument_rec(to_typecast_expr(src).op());
312311
}
313312
else if(src.id()==ID_address_of)
314313
{
315-
PRECONDITION(src.operands().size() == 1);
316-
if(src.op0().id()==ID_index)
314+
const exprt &object = to_address_of_expr(src).object();
315+
316+
if(object.id() == ID_index)
317317
{
318-
const auto &index_expr = to_index_expr(src.op0());
318+
const auto &index_expr = to_index_expr(object);
319319

320320
if(
321321
index_expr.array().id() == ID_string_constant &&
@@ -325,32 +325,91 @@ irep_idt get_string_argument_rec(const exprt &src)
325325
return to_string_constant(fmt_str).get_value();
326326
}
327327
}
328+
else if(object.id() == ID_string_constant)
329+
{
330+
return to_string_constant(object).get_value();
331+
}
328332
}
329333

330334
return irep_idt();
331335
}
332336

333-
irep_idt get_string_argument(const exprt &src, const namespacet &ns)
337+
static irep_idt get_string_argument(const exprt &src, const namespacet &ns)
334338
{
335339
exprt tmp=src;
336340
simplify(tmp, ns);
337341
return get_string_argument_rec(tmp);
338342
}
339343

344+
/// Return an expression if \p operands fulfills all criteria that we expect of
345+
/// the expression to be a variable argument list.
346+
static optionalt<exprt> get_va_args(const exprt::operandst &operands)
347+
{
348+
if(operands.size() != 2)
349+
return {};
350+
351+
const exprt &second_op = skip_typecast(operands.back());
352+
if(second_op.id() != ID_address_of)
353+
return {};
354+
355+
if(second_op.type() != pointer_type(pointer_type(empty_typet{})))
356+
return {};
357+
358+
const exprt &object = to_address_of_expr(second_op).object();
359+
if(object.id() != ID_index)
360+
return {};
361+
362+
const index_exprt &index_expr = to_index_expr(object);
363+
if(!index_expr.index().is_zero())
364+
return {};
365+
else
366+
return index_expr.array();
367+
}
368+
340369
void goto_symext::symex_printf(
341370
statet &state,
342371
const exprt &rhs)
343372
{
344373
PRECONDITION(!rhs.operands().empty());
345374

346-
exprt tmp_rhs = state.rename(rhs, ns).get();
375+
exprt tmp_rhs = rhs;
376+
clean_expr(tmp_rhs, state, false);
377+
tmp_rhs = state.rename(std::move(tmp_rhs), ns).get();
347378
do_simplify(tmp_rhs);
348379

349380
const exprt::operandst &operands=tmp_rhs.operands();
350381
std::list<exprt> args;
351382

352-
for(std::size_t i=1; i<operands.size(); i++)
353-
args.push_back(operands[i]);
383+
// we either have any number of operands or a va_list as second operand
384+
optionalt<exprt> va_args = get_va_args(operands);
385+
386+
if(!va_args.has_value())
387+
{
388+
args.insert(args.end(), std::next(operands.begin()), operands.end());
389+
}
390+
else
391+
{
392+
clean_expr(*va_args, state, false);
393+
*va_args = state.rename(*va_args, ns).get();
394+
if(va_args->id() != ID_array)
395+
{
396+
// we were not able to constant-propagate va_args, and thus don't have
397+
// sufficient information to generate printf -- give up
398+
return;
399+
}
400+
401+
for(const auto &op : va_args->operands())
402+
{
403+
exprt parameter = skip_typecast(op);
404+
if(parameter.id() == ID_address_of)
405+
parameter = to_address_of_expr(parameter).object();
406+
clean_expr(parameter, state, false);
407+
parameter = state.rename(std::move(parameter), ns).get();
408+
do_simplify(parameter);
409+
410+
args.push_back(std::move(parameter));
411+
}
412+
}
354413

355414
const irep_idt format_string=
356415
get_string_argument(operands[0], ns);

0 commit comments

Comments
 (0)